@nocobase/flow-engine 2.0.0-alpha.36 → 2.0.0-alpha.37
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/data-source/index.d.ts +1 -0
- package/lib/data-source/index.js +17 -0
- package/lib/executor/FlowExecutor.js +15 -1
- package/lib/flow-registry/GlobalFlowRegistry.d.ts +1 -0
- package/lib/flow-registry/GlobalFlowRegistry.js +3 -0
- package/lib/flow-registry/InstanceFlowRegistry.d.ts +1 -0
- package/lib/flow-registry/InstanceFlowRegistry.js +3 -0
- package/lib/flowContext.d.ts +3 -0
- package/lib/models/flowModel.js +8 -7
- package/lib/utils/flows.d.ts +10 -0
- package/lib/utils/flows.js +48 -0
- package/lib/utils/index.d.ts +2 -0
- package/lib/utils/index.js +6 -0
- package/package.json +4 -4
- package/src/data-source/index.ts +17 -0
- package/src/executor/FlowExecutor.ts +18 -1
- package/src/flow-registry/GlobalFlowRegistry.ts +1 -0
- package/src/flow-registry/InstanceFlowRegistry.ts +1 -0
- package/src/flow-registry/__tests__/globalFlowRegistry.test.ts +54 -0
- package/src/flowContext.ts +1 -0
- package/src/models/__tests__/dispatchEvent.behavior.test.ts +169 -0
- package/src/models/__tests__/flowModel.getFlows.sort.test.ts +29 -5
- package/src/models/__tests__/flowModel.test.ts +3 -3
- package/src/models/flowModel.tsx +14 -9
- package/src/utils/__tests__/flows.test.ts +65 -0
- package/src/utils/flows.ts +23 -0
- package/src/utils/index.ts +4 -0
|
@@ -82,6 +82,7 @@ export declare class CollectionManager {
|
|
|
82
82
|
getCollections(): Collection[];
|
|
83
83
|
clearCollections(): void;
|
|
84
84
|
getAssociation(associationName: string): CollectionField | undefined;
|
|
85
|
+
getChildrenCollections(name: any): any[];
|
|
85
86
|
}
|
|
86
87
|
export declare class Collection {
|
|
87
88
|
fields: Map<string, CollectionField>;
|
package/lib/data-source/index.js
CHANGED
|
@@ -300,6 +300,23 @@ const _CollectionManager = class _CollectionManager {
|
|
|
300
300
|
}
|
|
301
301
|
return collection.getField(fieldName);
|
|
302
302
|
}
|
|
303
|
+
getChildrenCollections(name) {
|
|
304
|
+
const childrens = [];
|
|
305
|
+
const collections = Array.from(this.collections.values());
|
|
306
|
+
const getChildrens = /* @__PURE__ */ __name((name2) => {
|
|
307
|
+
const inheritCollections = collections.filter((v) => {
|
|
308
|
+
var _a;
|
|
309
|
+
return (_a = v.options.inherits) == null ? void 0 : _a.includes(name2);
|
|
310
|
+
});
|
|
311
|
+
inheritCollections.forEach((v) => {
|
|
312
|
+
const collectionKey = v.name;
|
|
313
|
+
childrens.push(v);
|
|
314
|
+
return getChildrens(collectionKey);
|
|
315
|
+
});
|
|
316
|
+
return childrens;
|
|
317
|
+
}, "getChildrens");
|
|
318
|
+
return getChildrens(name);
|
|
319
|
+
}
|
|
303
320
|
};
|
|
304
321
|
__name(_CollectionManager, "CollectionManager");
|
|
305
322
|
let CollectionManager = _CollectionManager;
|
|
@@ -228,7 +228,21 @@ const _FlowExecutor = class _FlowExecutor {
|
|
|
228
228
|
});
|
|
229
229
|
const execute = /* @__PURE__ */ __name(async () => {
|
|
230
230
|
if (sequential) {
|
|
231
|
-
const
|
|
231
|
+
const flowsWithIndex = flows.map((f, i) => ({ f, i }));
|
|
232
|
+
const ordered = flowsWithIndex.slice().sort((a, b) => {
|
|
233
|
+
var _a2, _b2;
|
|
234
|
+
const regA = a.f["flowRegistry"];
|
|
235
|
+
const regB = b.f["flowRegistry"];
|
|
236
|
+
const typeA = (_a2 = regA == null ? void 0 : regA.constructor) == null ? void 0 : _a2._type;
|
|
237
|
+
const typeB = (_b2 = regB == null ? void 0 : regB.constructor) == null ? void 0 : _b2._type;
|
|
238
|
+
const groupA = typeA === "instance" ? 0 : 1;
|
|
239
|
+
const groupB = typeB === "instance" ? 0 : 1;
|
|
240
|
+
if (groupA !== groupB) return groupA - groupB;
|
|
241
|
+
const sa = a.f.sort ?? 0;
|
|
242
|
+
const sb = b.f.sort ?? 0;
|
|
243
|
+
if (sa !== sb) return sa - sb;
|
|
244
|
+
return a.i - b.i;
|
|
245
|
+
}).map((x) => x.f);
|
|
232
246
|
const results2 = [];
|
|
233
247
|
for (const flow of ordered) {
|
|
234
248
|
try {
|
|
@@ -12,6 +12,7 @@ import { BaseFlowRegistry } from './BaseFlowRegistry';
|
|
|
12
12
|
type FlowKey = string;
|
|
13
13
|
export declare class GlobalFlowRegistry extends BaseFlowRegistry {
|
|
14
14
|
protected target: ModelConstructor;
|
|
15
|
+
static readonly _type: "global";
|
|
15
16
|
constructor(target: ModelConstructor);
|
|
16
17
|
removeFlow(flowKey: FlowKey): void;
|
|
17
18
|
getFlow(flowKey: FlowKey): FlowDefinition | undefined;
|
|
@@ -11,6 +11,7 @@ var __defProp = Object.defineProperty;
|
|
|
11
11
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
12
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
13
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
14
15
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
16
|
var __export = (target, all) => {
|
|
16
17
|
for (var name in all)
|
|
@@ -25,6 +26,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
26
|
return to;
|
|
26
27
|
};
|
|
27
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
28
30
|
var GlobalFlowRegistry_exports = {};
|
|
29
31
|
__export(GlobalFlowRegistry_exports, {
|
|
30
32
|
GlobalFlowRegistry: () => GlobalFlowRegistry
|
|
@@ -88,6 +90,7 @@ const _GlobalFlowRegistry = class _GlobalFlowRegistry extends import_BaseFlowReg
|
|
|
88
90
|
}
|
|
89
91
|
};
|
|
90
92
|
__name(_GlobalFlowRegistry, "GlobalFlowRegistry");
|
|
93
|
+
__publicField(_GlobalFlowRegistry, "_type", "global");
|
|
91
94
|
let GlobalFlowRegistry = _GlobalFlowRegistry;
|
|
92
95
|
// Annotate the CommonJS export names for ESM import in node:
|
|
93
96
|
0 && (module.exports = {
|
|
@@ -12,6 +12,7 @@ import { BaseFlowRegistry } from './BaseFlowRegistry';
|
|
|
12
12
|
type FlowKey = string;
|
|
13
13
|
export declare class InstanceFlowRegistry extends BaseFlowRegistry {
|
|
14
14
|
protected model: FlowModel;
|
|
15
|
+
static readonly _type: "instance";
|
|
15
16
|
constructor(model: FlowModel);
|
|
16
17
|
save(): Promise<void>;
|
|
17
18
|
saveFlow(flow: FlowDefinition): Promise<void>;
|
|
@@ -11,6 +11,7 @@ var __defProp = Object.defineProperty;
|
|
|
11
11
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
12
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
13
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
14
15
|
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
16
|
var __export = (target, all) => {
|
|
16
17
|
for (var name in all)
|
|
@@ -25,6 +26,7 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
25
26
|
return to;
|
|
26
27
|
};
|
|
27
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
28
30
|
var InstanceFlowRegistry_exports = {};
|
|
29
31
|
__export(InstanceFlowRegistry_exports, {
|
|
30
32
|
InstanceFlowRegistry: () => InstanceFlowRegistry
|
|
@@ -52,6 +54,7 @@ const _InstanceFlowRegistry = class _InstanceFlowRegistry extends import_BaseFlo
|
|
|
52
54
|
}
|
|
53
55
|
};
|
|
54
56
|
__name(_InstanceFlowRegistry, "InstanceFlowRegistry");
|
|
57
|
+
__publicField(_InstanceFlowRegistry, "_type", "instance");
|
|
55
58
|
let InstanceFlowRegistry = _InstanceFlowRegistry;
|
|
56
59
|
// Annotate the CommonJS export names for ESM import in node:
|
|
57
60
|
0 && (module.exports = {
|
package/lib/flowContext.d.ts
CHANGED
|
@@ -146,6 +146,9 @@ declare class BaseFlowEngineContext extends FlowContext {
|
|
|
146
146
|
requireAsync: (url: string) => Promise<any>;
|
|
147
147
|
importAsync: (url: string) => Promise<any>;
|
|
148
148
|
createJSRunner: (options?: JSRunnerOptions) => JSRunner;
|
|
149
|
+
pageInfo: {
|
|
150
|
+
version?: 'v1' | 'v2';
|
|
151
|
+
};
|
|
149
152
|
/**
|
|
150
153
|
* @deprecated use `resolveJsonTemplate` instead
|
|
151
154
|
*/
|
package/lib/models/flowModel.js
CHANGED
|
@@ -532,14 +532,15 @@ const _FlowModel = class _FlowModel {
|
|
|
532
532
|
const instanceKeys = new Set(instanceFlows.keys());
|
|
533
533
|
const staticEntries = Array.from(staticFlows.entries()).filter(([key]) => !instanceKeys.has(key));
|
|
534
534
|
const instanceEntries = Array.from(instanceFlows.entries());
|
|
535
|
-
const
|
|
536
|
-
|
|
537
|
-
const sa = a.sort ?? 0;
|
|
538
|
-
const sb = b.sort ?? 0;
|
|
535
|
+
const instanceEntriesWithIndex = instanceEntries.map((e, i) => ({ e, i }));
|
|
536
|
+
instanceEntriesWithIndex.sort((a, b) => {
|
|
537
|
+
const sa = a.e[1].sort ?? 0;
|
|
538
|
+
const sb = b.e[1].sort ?? 0;
|
|
539
539
|
if (sa !== sb) return sa - sb;
|
|
540
|
-
return
|
|
540
|
+
return a.i - b.i;
|
|
541
541
|
});
|
|
542
|
-
|
|
542
|
+
const merged = [...instanceEntriesWithIndex.map(({ e }) => e), ...staticEntries];
|
|
543
|
+
return new Map(merged);
|
|
543
544
|
}
|
|
544
545
|
setProps(props, value) {
|
|
545
546
|
if (typeof props === "string") {
|
|
@@ -611,7 +612,7 @@ const _FlowModel = class _FlowModel {
|
|
|
611
612
|
}
|
|
612
613
|
async dispatchEvent(eventName, inputArgs, options) {
|
|
613
614
|
const isBeforeRender = eventName === "beforeRender";
|
|
614
|
-
const defaults = isBeforeRender ? { sequential: true, useCache: true } : {};
|
|
615
|
+
const defaults = isBeforeRender ? { sequential: true, useCache: true } : { sequential: true };
|
|
615
616
|
const execOptions = {
|
|
616
617
|
sequential: (options == null ? void 0 : options.sequential) ?? defaults.sequential,
|
|
617
618
|
useCache: (options == null ? void 0 : options.useCache) ?? defaults.useCache
|
|
@@ -0,0 +1,10 @@
|
|
|
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
|
+
import { FlowDefinition } from '../FlowDefinition';
|
|
10
|
+
export declare const isBeforeRenderFlow: (flow: FlowDefinition) => boolean;
|
|
@@ -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 __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
15
|
+
var __export = (target, all) => {
|
|
16
|
+
for (var name in all)
|
|
17
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
18
|
+
};
|
|
19
|
+
var __copyProps = (to, from, except, desc) => {
|
|
20
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
21
|
+
for (let key of __getOwnPropNames(from))
|
|
22
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
23
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
24
|
+
}
|
|
25
|
+
return to;
|
|
26
|
+
};
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var flows_exports = {};
|
|
29
|
+
__export(flows_exports, {
|
|
30
|
+
isBeforeRenderFlow: () => isBeforeRenderFlow
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(flows_exports);
|
|
33
|
+
const isBeforeRenderFlow = /* @__PURE__ */ __name((flow) => {
|
|
34
|
+
if (typeof flow.on === "string") {
|
|
35
|
+
return flow.on === "beforeRender";
|
|
36
|
+
}
|
|
37
|
+
if (typeof flow.on === "object") {
|
|
38
|
+
return flow.on.eventName === "beforeRender";
|
|
39
|
+
}
|
|
40
|
+
if (!flow.on && flow.manual !== true) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}, "isBeforeRenderFlow");
|
|
45
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
46
|
+
0 && (module.exports = {
|
|
47
|
+
isBeforeRenderFlow
|
|
48
|
+
});
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -22,4 +22,6 @@ export { clearAutoFlowError, getAutoFlowError, setAutoFlowError, type AutoFlowEr
|
|
|
22
22
|
export { parsePathnameToViewParams, type ViewParam } from './parsePathnameToViewParams';
|
|
23
23
|
export { buildSettingsViewInputArgs } from './buildSettingsViewInputArgs';
|
|
24
24
|
export { createSafeDocument, createSafeWindow, createSafeNavigator } from './safeGlobals';
|
|
25
|
+
export { createEphemeralContext } from './createEphemeralContext';
|
|
25
26
|
export { pruneFilter } from './pruneFilter';
|
|
27
|
+
export { isBeforeRenderFlow } from './flows';
|
package/lib/utils/index.js
CHANGED
|
@@ -40,6 +40,7 @@ __export(utils_exports, {
|
|
|
40
40
|
createAssociationSubpathResolver: () => import_associationObjectVariable.createAssociationSubpathResolver,
|
|
41
41
|
createCollectionContextMeta: () => import_createCollectionContextMeta.createCollectionContextMeta,
|
|
42
42
|
createCurrentRecordMetaFactory: () => import_variablesParams.createCurrentRecordMetaFactory,
|
|
43
|
+
createEphemeralContext: () => import_createEphemeralContext.createEphemeralContext,
|
|
43
44
|
createRecordMetaFactory: () => import_variablesParams.createRecordMetaFactory,
|
|
44
45
|
createSafeDocument: () => import_safeGlobals.createSafeDocument,
|
|
45
46
|
createSafeNavigator: () => import_safeGlobals.createSafeNavigator,
|
|
@@ -53,6 +54,7 @@ __export(utils_exports, {
|
|
|
53
54
|
getAutoFlowError: () => import_autoFlowError.getAutoFlowError,
|
|
54
55
|
getT: () => import_translation.getT,
|
|
55
56
|
inferRecordRef: () => import_variablesParams.inferRecordRef,
|
|
57
|
+
isBeforeRenderFlow: () => import_flows.isBeforeRenderFlow,
|
|
56
58
|
isInheritedFrom: () => import_inheritance.isInheritedFrom,
|
|
57
59
|
isVariableExpression: () => import_context.isVariableExpression,
|
|
58
60
|
parsePathnameToViewParams: () => import_parsePathnameToViewParams.parsePathnameToViewParams,
|
|
@@ -83,7 +85,9 @@ var import_autoFlowError = require("./autoFlowError");
|
|
|
83
85
|
var import_parsePathnameToViewParams = require("./parsePathnameToViewParams");
|
|
84
86
|
var import_buildSettingsViewInputArgs = require("./buildSettingsViewInputArgs");
|
|
85
87
|
var import_safeGlobals = require("./safeGlobals");
|
|
88
|
+
var import_createEphemeralContext = require("./createEphemeralContext");
|
|
86
89
|
var import_pruneFilter = require("./pruneFilter");
|
|
90
|
+
var import_flows = require("./flows");
|
|
87
91
|
// Annotate the CommonJS export names for ESM import in node:
|
|
88
92
|
0 && (module.exports = {
|
|
89
93
|
BLOCK_GROUP_CONFIGS,
|
|
@@ -100,6 +104,7 @@ var import_pruneFilter = require("./pruneFilter");
|
|
|
100
104
|
createAssociationSubpathResolver,
|
|
101
105
|
createCollectionContextMeta,
|
|
102
106
|
createCurrentRecordMetaFactory,
|
|
107
|
+
createEphemeralContext,
|
|
103
108
|
createRecordMetaFactory,
|
|
104
109
|
createSafeDocument,
|
|
105
110
|
createSafeNavigator,
|
|
@@ -113,6 +118,7 @@ var import_pruneFilter = require("./pruneFilter");
|
|
|
113
118
|
getAutoFlowError,
|
|
114
119
|
getT,
|
|
115
120
|
inferRecordRef,
|
|
121
|
+
isBeforeRenderFlow,
|
|
116
122
|
isInheritedFrom,
|
|
117
123
|
isVariableExpression,
|
|
118
124
|
parsePathnameToViewParams,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/flow-engine",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.37",
|
|
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.0-alpha.
|
|
12
|
-
"@nocobase/shared": "2.0.0-alpha.
|
|
11
|
+
"@nocobase/sdk": "2.0.0-alpha.37",
|
|
12
|
+
"@nocobase/shared": "2.0.0-alpha.37",
|
|
13
13
|
"ahooks": "^3.7.2",
|
|
14
14
|
"dompurify": "^3.0.2",
|
|
15
15
|
"lodash": "^4.x",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
],
|
|
36
36
|
"author": "NocoBase Team",
|
|
37
37
|
"license": "AGPL-3.0",
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "56e2dc3a83b8ca7afa97cfa71b37a43f2ec10396"
|
|
39
39
|
}
|
package/src/data-source/index.ts
CHANGED
|
@@ -321,6 +321,23 @@ export class CollectionManager {
|
|
|
321
321
|
}
|
|
322
322
|
return collection.getField(fieldName);
|
|
323
323
|
}
|
|
324
|
+
|
|
325
|
+
getChildrenCollections(name) {
|
|
326
|
+
const childrens = [];
|
|
327
|
+
const collections = Array.from(this.collections.values());
|
|
328
|
+
const getChildrens = (name) => {
|
|
329
|
+
const inheritCollections = collections.filter((v: any) => {
|
|
330
|
+
return v.options.inherits?.includes(name);
|
|
331
|
+
});
|
|
332
|
+
inheritCollections.forEach((v) => {
|
|
333
|
+
const collectionKey = v.name;
|
|
334
|
+
childrens.push(v);
|
|
335
|
+
return getChildrens(collectionKey);
|
|
336
|
+
});
|
|
337
|
+
return childrens;
|
|
338
|
+
};
|
|
339
|
+
return getChildrens(name);
|
|
340
|
+
}
|
|
324
341
|
}
|
|
325
342
|
|
|
326
343
|
// Collection 负责管理自己的 Field
|
|
@@ -240,7 +240,24 @@ export class FlowExecutor {
|
|
|
240
240
|
// 组装执行函数(返回值用于缓存;beforeRender 返回 results:any[],其它返回 true)
|
|
241
241
|
const execute = async () => {
|
|
242
242
|
if (sequential) {
|
|
243
|
-
|
|
243
|
+
// 顺序执行:动态流(实例级)优先,其次静态流;各自组内再按 sort 升序,最后保持原始顺序稳定
|
|
244
|
+
const flowsWithIndex = flows.map((f, i) => ({ f, i }));
|
|
245
|
+
const ordered = flowsWithIndex
|
|
246
|
+
.slice()
|
|
247
|
+
.sort((a, b) => {
|
|
248
|
+
const regA = a.f['flowRegistry'] as any;
|
|
249
|
+
const regB = b.f['flowRegistry'] as any;
|
|
250
|
+
const typeA = regA?.constructor?._type as 'instance' | 'global' | undefined;
|
|
251
|
+
const typeB = regB?.constructor?._type as 'instance' | 'global' | undefined;
|
|
252
|
+
const groupA = typeA === 'instance' ? 0 : 1; // 实例=0,静态=1
|
|
253
|
+
const groupB = typeB === 'instance' ? 0 : 1;
|
|
254
|
+
if (groupA !== groupB) return groupA - groupB;
|
|
255
|
+
const sa = a.f.sort ?? 0;
|
|
256
|
+
const sb = b.f.sort ?? 0;
|
|
257
|
+
if (sa !== sb) return sa - sb;
|
|
258
|
+
return a.i - b.i; // 稳定排序:保持原有相对顺序
|
|
259
|
+
})
|
|
260
|
+
.map((x) => x.f);
|
|
244
261
|
const results: any[] = [];
|
|
245
262
|
for (const flow of ordered) {
|
|
246
263
|
try {
|
|
@@ -138,4 +138,58 @@ describe('GlobalFlowRegistry', () => {
|
|
|
138
138
|
expect(staticHandler).toHaveBeenCalled();
|
|
139
139
|
expect(instanceHandler).toHaveBeenCalled();
|
|
140
140
|
});
|
|
141
|
+
|
|
142
|
+
test('getFlows returns instance (dynamic) flows before static flows with own internal ordering', () => {
|
|
143
|
+
const engine = new FlowEngine();
|
|
144
|
+
class OrderModel extends FlowModel {}
|
|
145
|
+
engine.registerModels({ OrderModel });
|
|
146
|
+
|
|
147
|
+
// Register static flows with various sort values
|
|
148
|
+
OrderModel.registerFlow({ key: 'staticLow', title: 'S-low', sort: -10, steps: {} });
|
|
149
|
+
OrderModel.registerFlow({ key: 'staticHigh', title: 'S-high', sort: 10, steps: {} });
|
|
150
|
+
|
|
151
|
+
const model = engine.createModel({ use: 'OrderModel' });
|
|
152
|
+
|
|
153
|
+
// Add instance flows with sort values that would normally interleave if sorted purely by sort
|
|
154
|
+
model.flowRegistry.addFlow('instMid', { title: 'I-mid', sort: 0, steps: {} });
|
|
155
|
+
model.flowRegistry.addFlow('instHigh', { title: 'I-high', sort: 5, steps: {} });
|
|
156
|
+
|
|
157
|
+
const keys = Array.from(model.getFlows().keys());
|
|
158
|
+
// Expect dynamic flows first (by sort inside group), then static flows (by their own ordering)
|
|
159
|
+
expect(keys).toEqual(['instMid', 'instHigh', 'staticLow', 'staticHigh']);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('sequential dispatch runs instance flows before static flows regardless of static sort', async () => {
|
|
163
|
+
const engine = new FlowEngine();
|
|
164
|
+
class SeqModel extends FlowModel {}
|
|
165
|
+
engine.registerModels({ SeqModel });
|
|
166
|
+
|
|
167
|
+
const calls: string[] = [];
|
|
168
|
+
|
|
169
|
+
// Static flow with very low sort to try to run first if not grouped
|
|
170
|
+
SeqModel.registerFlow({
|
|
171
|
+
key: 'S',
|
|
172
|
+
on: { eventName: 'click' },
|
|
173
|
+
sort: -100,
|
|
174
|
+
steps: { s: { handler: async () => void calls.push('static') } as any },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const model = engine.createModel({ use: 'SeqModel' });
|
|
178
|
+
|
|
179
|
+
// Instance flows with sort greater than static
|
|
180
|
+
model.flowRegistry.addFlow('I1', {
|
|
181
|
+
on: { eventName: 'click' },
|
|
182
|
+
sort: 0,
|
|
183
|
+
steps: { i1: { handler: async () => void calls.push('inst1') } as any },
|
|
184
|
+
});
|
|
185
|
+
model.flowRegistry.addFlow('I2', {
|
|
186
|
+
on: { eventName: 'click' },
|
|
187
|
+
sort: 5,
|
|
188
|
+
steps: { i2: { handler: async () => void calls.push('inst2') } as any },
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await model.dispatchEvent('click', undefined, { sequential: true });
|
|
192
|
+
|
|
193
|
+
expect(calls).toEqual(['inst1', 'inst2', 'static']);
|
|
194
|
+
});
|
|
141
195
|
});
|
package/src/flowContext.ts
CHANGED
|
@@ -896,6 +896,7 @@ class BaseFlowEngineContext extends FlowContext {
|
|
|
896
896
|
declare requireAsync: (url: string) => Promise<any>;
|
|
897
897
|
declare importAsync: (url: string) => Promise<any>;
|
|
898
898
|
declare createJSRunner: (options?: JSRunnerOptions) => JSRunner;
|
|
899
|
+
declare pageInfo: { version?: 'v1' | 'v2' };
|
|
899
900
|
/**
|
|
900
901
|
* @deprecated use `resolveJsonTemplate` instead
|
|
901
902
|
*/
|
|
@@ -0,0 +1,169 @@
|
|
|
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
|
+
import { describe, test, expect } from 'vitest';
|
|
11
|
+
import { FlowEngine } from '../../flowEngine';
|
|
12
|
+
import { FlowModel } from '../../models/flowModel';
|
|
13
|
+
|
|
14
|
+
describe('dispatchEvent behavior (defaults, parallel, errors)', () => {
|
|
15
|
+
test('non-beforeRender defaults to sequential execution and respects getEventFlows order', async () => {
|
|
16
|
+
const engine = new FlowEngine();
|
|
17
|
+
class M extends FlowModel {}
|
|
18
|
+
engine.registerModels({ M });
|
|
19
|
+
|
|
20
|
+
const calls: string[] = [];
|
|
21
|
+
|
|
22
|
+
// Static flow with higher sort
|
|
23
|
+
M.registerFlow({
|
|
24
|
+
key: 'S',
|
|
25
|
+
on: { eventName: 'go' },
|
|
26
|
+
sort: 10,
|
|
27
|
+
steps: { s: { handler: async () => void calls.push('static') } as any },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const model = engine.createModel({ use: 'M' });
|
|
31
|
+
|
|
32
|
+
// Instance flows
|
|
33
|
+
model.registerFlow('I1', {
|
|
34
|
+
on: { eventName: 'go' },
|
|
35
|
+
sort: 0,
|
|
36
|
+
steps: { i1: { handler: async () => void calls.push('inst1') } as any },
|
|
37
|
+
});
|
|
38
|
+
model.registerFlow('I2', {
|
|
39
|
+
on: { eventName: 'go' },
|
|
40
|
+
sort: 5,
|
|
41
|
+
steps: { i2: { handler: async () => void calls.push('inst2') } as any },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// No options provided -> should default to sequential execution
|
|
45
|
+
await model.dispatchEvent('go');
|
|
46
|
+
|
|
47
|
+
// Expected order should match getEventFlows('go') order
|
|
48
|
+
const expected = model.getEventFlows('go').map((f) => f.key);
|
|
49
|
+
expect(calls).toEqual(expected.map((k) => (k === 'I1' ? 'inst1' : k === 'I2' ? 'inst2' : 'static')));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('parallel mode (sequential=false) runs all and tolerates non-beforeRender errors', async () => {
|
|
53
|
+
const engine = new FlowEngine();
|
|
54
|
+
class M extends FlowModel {}
|
|
55
|
+
engine.registerModels({ M });
|
|
56
|
+
|
|
57
|
+
const calls: string[] = [];
|
|
58
|
+
|
|
59
|
+
M.registerFlow({
|
|
60
|
+
key: 'S',
|
|
61
|
+
on: { eventName: 'go' },
|
|
62
|
+
sort: 1,
|
|
63
|
+
steps: { s: { handler: async () => 'static-ok' } as any },
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const model = engine.createModel({ use: 'M' });
|
|
67
|
+
|
|
68
|
+
model.registerFlow('I1', {
|
|
69
|
+
on: { eventName: 'go' },
|
|
70
|
+
sort: 0,
|
|
71
|
+
steps: { i1: { handler: async () => (calls.push('inst1'), 'i1-ok') } as any },
|
|
72
|
+
});
|
|
73
|
+
model.registerFlow('I2', {
|
|
74
|
+
on: { eventName: 'go' },
|
|
75
|
+
sort: 2,
|
|
76
|
+
steps: { i2: { handler: async () => (calls.push('inst2'), 'i2-ok') } as any },
|
|
77
|
+
});
|
|
78
|
+
model.registerFlow('ERR', {
|
|
79
|
+
on: { eventName: 'go' },
|
|
80
|
+
sort: 3,
|
|
81
|
+
steps: {
|
|
82
|
+
e: {
|
|
83
|
+
handler: async () => {
|
|
84
|
+
calls.push('err');
|
|
85
|
+
throw new Error('boom');
|
|
86
|
+
},
|
|
87
|
+
} as any,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const results = await model.dispatchEvent('go', undefined, { sequential: false });
|
|
92
|
+
|
|
93
|
+
// All handlers ran; order is not guaranteed in parallel mode
|
|
94
|
+
expect(new Set(calls)).toEqual(new Set(['inst1', 'inst2', 'err']));
|
|
95
|
+
// The error flow returns undefined and is filtered out; others return stepResults objects
|
|
96
|
+
// Expect one result object per successful flow with its step key
|
|
97
|
+
expect(Array.isArray(results)).toBe(true);
|
|
98
|
+
const keys = (results as any[]).flatMap((o) => Object.keys(o));
|
|
99
|
+
expect(new Set(keys)).toEqual(new Set(['i1', 'i2', 's']));
|
|
100
|
+
const byKey = Object.assign({}, ...(results as any[]));
|
|
101
|
+
expect(byKey['i1']).toBe('i1-ok');
|
|
102
|
+
expect(byKey['i2']).toBe('i2-ok');
|
|
103
|
+
expect(byKey['s']).toBe('static-ok');
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('sequential non-beforeRender: stops subsequent flows on error', async () => {
|
|
107
|
+
const engine = new FlowEngine();
|
|
108
|
+
class M extends FlowModel {}
|
|
109
|
+
engine.registerModels({ M });
|
|
110
|
+
|
|
111
|
+
const calls: string[] = [];
|
|
112
|
+
const model = engine.createModel({ use: 'M' });
|
|
113
|
+
|
|
114
|
+
model.registerFlow('I1', {
|
|
115
|
+
on: { eventName: 'go' },
|
|
116
|
+
sort: 0,
|
|
117
|
+
steps: { i1: { handler: async () => void calls.push('i1') } as any },
|
|
118
|
+
});
|
|
119
|
+
model.registerFlow('ERR', {
|
|
120
|
+
on: { eventName: 'go' },
|
|
121
|
+
sort: 1,
|
|
122
|
+
steps: {
|
|
123
|
+
e: {
|
|
124
|
+
handler: async () => {
|
|
125
|
+
throw new Error('X');
|
|
126
|
+
},
|
|
127
|
+
} as any,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
model.registerFlow('I2', {
|
|
131
|
+
on: { eventName: 'go' },
|
|
132
|
+
sort: 2,
|
|
133
|
+
steps: { i2: { handler: async () => void calls.push('i2') } as any },
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await model.dispatchEvent('go', undefined, { sequential: true }).catch(() => undefined);
|
|
137
|
+
// 只应执行第一个 flow,后续被中止
|
|
138
|
+
expect(calls).toEqual(['i1']);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test('beforeRender errors are thrown (sequential default)', async () => {
|
|
142
|
+
const engine = new FlowEngine();
|
|
143
|
+
class M extends FlowModel {}
|
|
144
|
+
engine.registerModels({ M });
|
|
145
|
+
|
|
146
|
+
const calls: string[] = [];
|
|
147
|
+
const model = engine.createModel({ use: 'M' });
|
|
148
|
+
|
|
149
|
+
model.registerFlow('F1', {
|
|
150
|
+
on: 'beforeRender',
|
|
151
|
+
sort: 0,
|
|
152
|
+
steps: {
|
|
153
|
+
a: {
|
|
154
|
+
handler: async () => {
|
|
155
|
+
throw new Error('BR');
|
|
156
|
+
},
|
|
157
|
+
} as any,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
model.registerFlow('F2', {
|
|
161
|
+
on: 'beforeRender',
|
|
162
|
+
sort: 1,
|
|
163
|
+
steps: { b: { handler: async () => void calls.push('b') } as any },
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await expect(model.dispatchEvent('beforeRender', undefined, { useCache: false })).rejects.toThrow('BR');
|
|
167
|
+
expect(calls).toEqual([]);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -22,7 +22,7 @@ describe('FlowModel.getFlows sorting and getEventFlows(beforeRender) order', ()
|
|
|
22
22
|
model = new TestFlowModel({ flowEngine: fakeEngine } as any);
|
|
23
23
|
});
|
|
24
24
|
|
|
25
|
-
test('getFlows returns
|
|
25
|
+
test('getFlows returns dynamic(instance) flows first, each group ordered by sort ascending', () => {
|
|
26
26
|
// class-level (static) flows
|
|
27
27
|
TestFlowModel.registerFlow('flowA', { title: 'A', sort: 10, steps: {} });
|
|
28
28
|
TestFlowModel.registerFlow('flowB', { title: 'B', sort: 5, steps: {} });
|
|
@@ -34,7 +34,8 @@ describe('FlowModel.getFlows sorting and getEventFlows(beforeRender) order', ()
|
|
|
34
34
|
const flows = model.getFlows();
|
|
35
35
|
const orderedKeys = Array.from(flows.keys());
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
// 动态流组:flowD(0), flowC(7);静态流组:flowB(5), flowA(10)
|
|
38
|
+
expect(orderedKeys).toEqual(['flowD', 'flowC', 'flowB', 'flowA']);
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
test("getEventFlows('beforeRender') keeps getFlows order and filters out manual/on flows", () => {
|
|
@@ -54,21 +55,22 @@ describe('FlowModel.getFlows sorting and getEventFlows(beforeRender) order', ()
|
|
|
54
55
|
const autoFlowKeys = model.getEventFlows('beforeRender').map((f) => f.key);
|
|
55
56
|
|
|
56
57
|
// auto flows should exclude event/manual flows
|
|
57
|
-
|
|
58
|
+
// 新顺序:动态流组(flowD 0, flowC 7, flowE 8)→ 静态流组(flowB 5, flowA 10)
|
|
59
|
+
expect(autoFlowKeys).toEqual(['flowD', 'flowC', 'flowE', 'flowB', 'flowA']);
|
|
58
60
|
|
|
59
61
|
// relative order should match getFlows order (subset in same sequence)
|
|
60
62
|
const filteredGetFlowsOrder = getFlowsOrder.filter((k) => !['eventFlow', 'manualFlow'].includes(k));
|
|
61
63
|
expect(autoFlowKeys).toEqual(filteredGetFlowsOrder);
|
|
62
64
|
});
|
|
63
65
|
|
|
64
|
-
test('getFlows tie-breaker:
|
|
66
|
+
test('getFlows tie-breaker: instance before static when sort equal', () => {
|
|
65
67
|
// static flow with sort 2
|
|
66
68
|
TestFlowModel.registerFlow('static2', { title: 'S2', sort: 2, steps: {} });
|
|
67
69
|
// instance flow with same sort 2
|
|
68
70
|
model.registerFlow('instance2', { title: 'I2', sort: 2, steps: {} });
|
|
69
71
|
|
|
70
72
|
const keys = Array.from(model.getFlows().keys());
|
|
71
|
-
expect(keys.indexOf('
|
|
73
|
+
expect(keys.indexOf('instance2')).toBeLessThan(keys.indexOf('static2'));
|
|
72
74
|
});
|
|
73
75
|
|
|
74
76
|
test('getFlows tie-breaker: parent static before child static when sort equal', () => {
|
|
@@ -97,4 +99,26 @@ describe('FlowModel.getFlows sorting and getEventFlows(beforeRender) order', ()
|
|
|
97
99
|
const keys = Array.from(staticFlows.keys());
|
|
98
100
|
expect(keys.indexOf('p')).toBeLessThan(keys.indexOf('c'));
|
|
99
101
|
});
|
|
102
|
+
|
|
103
|
+
test('instance flows keep registration order when sort equal', () => {
|
|
104
|
+
// same sort for all instance flows -> keep registration order
|
|
105
|
+
model.registerFlow('i1', { title: 'I1', sort: 1, steps: {} } as any);
|
|
106
|
+
model.registerFlow('i2', { title: 'I2', sort: 1, steps: {} } as any);
|
|
107
|
+
model.registerFlow('i3', { title: 'I3', sort: 1, steps: {} } as any);
|
|
108
|
+
|
|
109
|
+
// add some static flows to ensure grouping doesn't affect instance intra-order
|
|
110
|
+
(TestFlowModel as any).registerFlow('s1', { title: 'S1', sort: 1, steps: {} });
|
|
111
|
+
(TestFlowModel as any).registerFlow('s2', { title: 'S2', sort: 2, steps: {} });
|
|
112
|
+
|
|
113
|
+
const keys = Array.from(model.getFlows().keys());
|
|
114
|
+
// Instance group appears first and preserves registration order
|
|
115
|
+
const posI1 = keys.indexOf('i1');
|
|
116
|
+
const posI2 = keys.indexOf('i2');
|
|
117
|
+
const posI3 = keys.indexOf('i3');
|
|
118
|
+
expect(posI1).toBeLessThan(posI2);
|
|
119
|
+
expect(posI2).toBeLessThan(posI3);
|
|
120
|
+
|
|
121
|
+
// Static group follows
|
|
122
|
+
expect(keys.slice(posI3 + 1)).toEqual(expect.arrayContaining(['s1', 's2']));
|
|
123
|
+
});
|
|
100
124
|
});
|
|
@@ -898,7 +898,7 @@ describe('FlowModel', () => {
|
|
|
898
898
|
expect(_dispatchEventSpy).toHaveBeenCalledWith(
|
|
899
899
|
'normalEvent',
|
|
900
900
|
{ data: 'test' },
|
|
901
|
-
expect.objectContaining({ sequential:
|
|
901
|
+
expect.objectContaining({ sequential: true }),
|
|
902
902
|
);
|
|
903
903
|
expect(_dispatchEventWithDebounceSpy).not.toHaveBeenCalled();
|
|
904
904
|
|
|
@@ -919,7 +919,7 @@ describe('FlowModel', () => {
|
|
|
919
919
|
expect(_dispatchEventSpy).toHaveBeenCalledWith(
|
|
920
920
|
'defaultEvent',
|
|
921
921
|
{ data: 'test' },
|
|
922
|
-
expect.objectContaining({ sequential:
|
|
922
|
+
expect.objectContaining({ sequential: true }),
|
|
923
923
|
);
|
|
924
924
|
expect(_dispatchEventWithDebounceSpy).not.toHaveBeenCalled();
|
|
925
925
|
|
|
@@ -940,7 +940,7 @@ describe('FlowModel', () => {
|
|
|
940
940
|
expect(_dispatchEventSpy).toHaveBeenCalledWith(
|
|
941
941
|
'undefinedOptionsEvent',
|
|
942
942
|
{ data: 'test' },
|
|
943
|
-
expect.objectContaining({ sequential:
|
|
943
|
+
expect.objectContaining({ sequential: true }),
|
|
944
944
|
);
|
|
945
945
|
expect(_dispatchEventWithDebounceSpy).not.toHaveBeenCalled();
|
|
946
946
|
|
package/src/models/flowModel.tsx
CHANGED
|
@@ -611,17 +611,22 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
611
611
|
const instanceKeys = new Set(instanceFlows.keys());
|
|
612
612
|
const staticEntries = Array.from(staticFlows.entries()).filter(([key]) => !instanceKeys.has(key));
|
|
613
613
|
|
|
614
|
-
//
|
|
614
|
+
// 组内排序:
|
|
615
|
+
// - 静态流:GlobalFlowRegistry 已按 sort 和继承深度排序,直接使用
|
|
616
|
+
// - 实例流:按 sort 升序;相同 sort 保持注册顺序
|
|
615
617
|
const instanceEntries = Array.from(instanceFlows.entries());
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
const sa = a.sort ?? 0;
|
|
619
|
-
const sb = b.sort ?? 0;
|
|
618
|
+
const instanceEntriesWithIndex = instanceEntries.map((e, i) => ({ e, i }));
|
|
619
|
+
instanceEntriesWithIndex.sort((a, b) => {
|
|
620
|
+
const sa = a.e[1].sort ?? 0;
|
|
621
|
+
const sb = b.e[1].sort ?? 0;
|
|
620
622
|
if (sa !== sb) return sa - sb;
|
|
621
|
-
return
|
|
623
|
+
return a.i - b.i; // 稳定顺序
|
|
622
624
|
});
|
|
623
625
|
|
|
624
|
-
|
|
626
|
+
// 分组合并:动态流(实例)优先于静态流
|
|
627
|
+
const merged: [string, FlowDefinition][] = [...instanceEntriesWithIndex.map(({ e }) => e), ...staticEntries];
|
|
628
|
+
|
|
629
|
+
return new Map<string, FlowDefinition>(merged);
|
|
625
630
|
}
|
|
626
631
|
|
|
627
632
|
setProps(props: IModelComponentProps): void;
|
|
@@ -733,8 +738,8 @@ export class FlowModel<Structure extends DefaultStructure = DefaultStructure> {
|
|
|
733
738
|
} & DispatchEventOptions,
|
|
734
739
|
): Promise<any[]> {
|
|
735
740
|
const isBeforeRender = eventName === 'beforeRender';
|
|
736
|
-
// 缺省值由模型层提供:beforeRender 默认顺序执行 +
|
|
737
|
-
const defaults = isBeforeRender ? { sequential: true, useCache: true } : {};
|
|
741
|
+
// 缺省值由模型层提供:beforeRender 默认顺序执行 + 使用缓存;其它事件默认顺序执行(不默认使用缓存)
|
|
742
|
+
const defaults = isBeforeRender ? { sequential: true, useCache: true } : { sequential: true };
|
|
738
743
|
const execOptions = {
|
|
739
744
|
sequential: options?.sequential ?? (defaults as any).sequential,
|
|
740
745
|
useCache: options?.useCache ?? (defaults as any).useCache,
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
import { describe, test, expect } from 'vitest';
|
|
11
|
+
import { FlowEngine } from '../../flowEngine';
|
|
12
|
+
import { FlowModel } from '../../models/flowModel';
|
|
13
|
+
import { isBeforeRenderFlow } from '../index';
|
|
14
|
+
|
|
15
|
+
describe('utils/isBeforeRenderFlow', () => {
|
|
16
|
+
test('identifies beforeRender by on string', () => {
|
|
17
|
+
const engine = new FlowEngine();
|
|
18
|
+
class M extends FlowModel {}
|
|
19
|
+
engine.registerModels({ M });
|
|
20
|
+
const m = engine.createModel({ use: 'M' });
|
|
21
|
+
m.registerFlow('A', { on: 'beforeRender', steps: {} });
|
|
22
|
+
const flow = m.flowRegistry.getFlow('A')!;
|
|
23
|
+
expect(isBeforeRenderFlow(flow)).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('identifies beforeRender by on object', () => {
|
|
27
|
+
const engine = new FlowEngine();
|
|
28
|
+
class M extends FlowModel {}
|
|
29
|
+
engine.registerModels({ M });
|
|
30
|
+
const m = engine.createModel({ use: 'M' });
|
|
31
|
+
m.registerFlow('B', { on: { eventName: 'beforeRender' }, steps: {} });
|
|
32
|
+
const flow = m.flowRegistry.getFlow('B')!;
|
|
33
|
+
expect(isBeforeRenderFlow(flow)).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('treats missing on and non-manual as beforeRender', () => {
|
|
37
|
+
const engine = new FlowEngine();
|
|
38
|
+
class M extends FlowModel {}
|
|
39
|
+
engine.registerModels({ M });
|
|
40
|
+
const m = engine.createModel({ use: 'M' });
|
|
41
|
+
m.registerFlow('C', { steps: {} });
|
|
42
|
+
const flow = m.flowRegistry.getFlow('C')!;
|
|
43
|
+
expect(isBeforeRenderFlow(flow)).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('manual flow is not beforeRender when on is missing', () => {
|
|
47
|
+
const engine = new FlowEngine();
|
|
48
|
+
class M extends FlowModel {}
|
|
49
|
+
engine.registerModels({ M });
|
|
50
|
+
const m = engine.createModel({ use: 'M' });
|
|
51
|
+
m.registerFlow('D', { manual: true, steps: {} });
|
|
52
|
+
const flow = m.flowRegistry.getFlow('D')!;
|
|
53
|
+
expect(isBeforeRenderFlow(flow)).toBe(false);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test('non-beforeRender event is not beforeRender', () => {
|
|
57
|
+
const engine = new FlowEngine();
|
|
58
|
+
class M extends FlowModel {}
|
|
59
|
+
engine.registerModels({ M });
|
|
60
|
+
const m = engine.createModel({ use: 'M' });
|
|
61
|
+
m.registerFlow('E', { on: { eventName: 'click' }, steps: {} });
|
|
62
|
+
const flow = m.flowRegistry.getFlow('E')!;
|
|
63
|
+
expect(isBeforeRenderFlow(flow)).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
import { FlowDefinition } from '../FlowDefinition';
|
|
11
|
+
|
|
12
|
+
export const isBeforeRenderFlow = (flow: FlowDefinition): boolean => {
|
|
13
|
+
if (typeof flow.on === 'string') {
|
|
14
|
+
return flow.on === 'beforeRender';
|
|
15
|
+
}
|
|
16
|
+
if (typeof flow.on === 'object') {
|
|
17
|
+
return flow.on.eventName === 'beforeRender';
|
|
18
|
+
}
|
|
19
|
+
if (!flow.on && flow.manual !== true) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
};
|
package/src/utils/index.ts
CHANGED
|
@@ -63,5 +63,9 @@ export { buildSettingsViewInputArgs } from './buildSettingsViewInputArgs';
|
|
|
63
63
|
// 安全全局对象(window/document)
|
|
64
64
|
export { createSafeDocument, createSafeWindow, createSafeNavigator } from './safeGlobals';
|
|
65
65
|
|
|
66
|
+
// Ephemeral context helper(用于临时注入属性/方法,避免污染父级 ctx)
|
|
67
|
+
export { createEphemeralContext } from './createEphemeralContext';
|
|
68
|
+
|
|
66
69
|
// Filter helpers
|
|
67
70
|
export { pruneFilter } from './pruneFilter';
|
|
71
|
+
export { isBeforeRenderFlow } from './flows';
|