@nocobase/flow-engine 2.0.0-alpha.34 → 2.0.0-alpha.35
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/ViewScopedFlowEngine.js +12 -0
- package/lib/emitter.d.ts +6 -0
- package/lib/emitter.js +12 -0
- package/lib/executor/FlowExecutor.js +20 -0
- package/lib/flowEngine.d.ts +16 -0
- package/lib/flowEngine.js +38 -0
- package/lib/models/flowModel.d.ts +2 -0
- package/lib/models/flowModel.js +14 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +51 -0
- package/lib/scheduler/ModelOperationScheduler.js +262 -0
- package/lib/views/DrawerComponent.js +1 -1
- package/lib/views/useDrawer.js +1 -1
- package/package.json +4 -4
- package/src/ViewScopedFlowEngine.ts +14 -0
- package/src/__tests__/modelOperationScheduler.test.ts +346 -0
- package/src/emitter.ts +14 -0
- package/src/executor/FlowExecutor.ts +20 -0
- package/src/flowEngine.ts +53 -0
- package/src/models/__tests__/flowModel.scheduleModelOperation.test.tsx +129 -0
- package/src/models/flowModel.tsx +19 -0
- package/src/scheduler/ModelOperationScheduler.ts +304 -0
- package/src/views/DrawerComponent.tsx +1 -1
- package/src/views/useDrawer.tsx +1 -1
|
@@ -37,6 +37,12 @@ function createViewScopedEngine(parent) {
|
|
|
37
37
|
local.setModelRepository(parent.modelRepository);
|
|
38
38
|
}
|
|
39
39
|
local.context.addDelegate(parent.context);
|
|
40
|
+
const originalUnlink = local.unlinkFromStack.bind(local);
|
|
41
|
+
local.unlinkFromStack = function() {
|
|
42
|
+
var _a;
|
|
43
|
+
(_a = local.disposeScheduler) == null ? void 0 : _a.call(local);
|
|
44
|
+
return originalUnlink();
|
|
45
|
+
};
|
|
40
46
|
const localOnly = /* @__PURE__ */ new Set([
|
|
41
47
|
"_modelInstances",
|
|
42
48
|
"_applyFlowCache",
|
|
@@ -44,6 +50,12 @@ function createViewScopedEngine(parent) {
|
|
|
44
50
|
"context",
|
|
45
51
|
"previousEngine",
|
|
46
52
|
"nextEngine",
|
|
53
|
+
// 调度器与事件总线局部化
|
|
54
|
+
"emitter",
|
|
55
|
+
"_modelOperationScheduler",
|
|
56
|
+
"getScheduler",
|
|
57
|
+
"scheduleModelOperation",
|
|
58
|
+
"disposeScheduler",
|
|
47
59
|
// 栈指针维护方法需要在本地执行,而非代理到父引擎
|
|
48
60
|
"unlinkFromStack",
|
|
49
61
|
"linkAfter",
|
package/lib/emitter.d.ts
CHANGED
|
@@ -13,4 +13,10 @@ export declare class Emitter {
|
|
|
13
13
|
on(event: string, callback: (...args: any[]) => void): void;
|
|
14
14
|
off(event: string, callback: (...args: any[]) => void): void;
|
|
15
15
|
emit(event: string, ...args: any[]): void;
|
|
16
|
+
/**
|
|
17
|
+
* 异步触发事件:按监听器注册顺序依次(串行)等待执行完成。
|
|
18
|
+
* - 若某个监听器抛错/Promise reject,将中断后续执行并将异常向上传递(便于调用方统一处理)。
|
|
19
|
+
* - 当前不提供并行触发能力;如需并发处理,请在监听器内部自行管理(例如在回调内使用 Promise.all)。
|
|
20
|
+
*/
|
|
21
|
+
emitAsync(event: string, ...args: any[]): Promise<void>;
|
|
16
22
|
}
|
package/lib/emitter.js
CHANGED
|
@@ -49,6 +49,18 @@ const _Emitter = class _Emitter {
|
|
|
49
49
|
}
|
|
50
50
|
(this.events[event] || []).forEach((fn) => fn(...args));
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* 异步触发事件:按监听器注册顺序依次(串行)等待执行完成。
|
|
54
|
+
* - 若某个监听器抛错/Promise reject,将中断后续执行并将异常向上传递(便于调用方统一处理)。
|
|
55
|
+
* - 当前不提供并行触发能力;如需并发处理,请在监听器内部自行管理(例如在回调内使用 Promise.all)。
|
|
56
|
+
*/
|
|
57
|
+
async emitAsync(event, ...args) {
|
|
58
|
+
if (this.paused) return;
|
|
59
|
+
const listeners = (this.events[event] || []).slice();
|
|
60
|
+
for (const fn of listeners) {
|
|
61
|
+
await fn(...args);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
52
64
|
};
|
|
53
65
|
__name(_Emitter, "Emitter");
|
|
54
66
|
let Emitter = _Emitter;
|
|
@@ -199,6 +199,12 @@ const _FlowExecutor = class _FlowExecutor {
|
|
|
199
199
|
const runId = `${model.uid}-${eventName}-${Date.now()}`;
|
|
200
200
|
const logger = model.context.logger;
|
|
201
201
|
try {
|
|
202
|
+
await this.engine.emitter.emitAsync(`model:event:${eventName}:start`, {
|
|
203
|
+
uid: model.uid,
|
|
204
|
+
model,
|
|
205
|
+
runId,
|
|
206
|
+
inputArgs
|
|
207
|
+
});
|
|
202
208
|
await ((_a = model.onDispatchEventStart) == null ? void 0 : _a.call(model, eventName, options, inputArgs));
|
|
203
209
|
} catch (err) {
|
|
204
210
|
if (isBeforeRender && err instanceof import_utils.FlowExitException) {
|
|
@@ -275,6 +281,13 @@ const _FlowExecutor = class _FlowExecutor {
|
|
|
275
281
|
} catch (hookErr) {
|
|
276
282
|
logger.error({ err: hookErr }, `BaseModel.dispatchEvent: End hook error for event '${eventName}'`);
|
|
277
283
|
}
|
|
284
|
+
await this.engine.emitter.emitAsync(`model:event:${eventName}:end`, {
|
|
285
|
+
uid: model.uid,
|
|
286
|
+
model,
|
|
287
|
+
runId,
|
|
288
|
+
inputArgs,
|
|
289
|
+
result
|
|
290
|
+
});
|
|
278
291
|
return result;
|
|
279
292
|
} catch (error) {
|
|
280
293
|
try {
|
|
@@ -285,6 +298,13 @@ const _FlowExecutor = class _FlowExecutor {
|
|
|
285
298
|
{ err: error },
|
|
286
299
|
`BaseModel.dispatchEvent: Error executing event '${eventName}' for model '${model.uid}':`
|
|
287
300
|
);
|
|
301
|
+
await this.engine.emitter.emitAsync(`model:event:${eventName}:error`, {
|
|
302
|
+
uid: model.uid,
|
|
303
|
+
model,
|
|
304
|
+
runId,
|
|
305
|
+
inputArgs,
|
|
306
|
+
error
|
|
307
|
+
});
|
|
288
308
|
if (throwOnError) throw error;
|
|
289
309
|
}
|
|
290
310
|
}
|
package/lib/flowEngine.d.ts
CHANGED
|
@@ -5,6 +5,9 @@ import { FlowSettings } from './flowSettings';
|
|
|
5
5
|
import { FlowModel } from './models';
|
|
6
6
|
import { ReactView } from './ReactView';
|
|
7
7
|
import { FlowResource } from './resources';
|
|
8
|
+
import { Emitter } from './emitter';
|
|
9
|
+
import ModelOperationScheduler from './scheduler/ModelOperationScheduler';
|
|
10
|
+
import type { ScheduleOptions, ScheduledCancel } from './scheduler/ModelOperationScheduler';
|
|
8
11
|
import type { ActionDefinition, ApplyFlowCacheEntry, CreateModelOptions, EventDefinition, FlowModelOptions, IFlowModelRepository, ModelConstructor, PersistOptions, ResourceType } from './types';
|
|
9
12
|
/**
|
|
10
13
|
* FlowEngine is the core class of the flow engine, responsible for managing flow models, actions, model repository, and more.
|
|
@@ -81,6 +84,13 @@ export declare class FlowEngine {
|
|
|
81
84
|
private _previousEngine?;
|
|
82
85
|
private _nextEngine?;
|
|
83
86
|
private _resources;
|
|
87
|
+
/**
|
|
88
|
+
* 引擎事件总线(目前用于模型生命周期等事件)。
|
|
89
|
+
* ViewScopedFlowEngine 持有自己的实例,实现作用域隔离。
|
|
90
|
+
*/
|
|
91
|
+
emitter: Emitter;
|
|
92
|
+
/** 调度器:仅在 View 作用域引擎本地启用;根/Block 作用域默认不持有 */
|
|
93
|
+
private _modelOperationScheduler?;
|
|
84
94
|
logger: pino.Logger;
|
|
85
95
|
/**
|
|
86
96
|
* Flow settings, including components and form scopes.
|
|
@@ -102,6 +112,12 @@ export declare class FlowEngine {
|
|
|
102
112
|
* Constructor. Initializes React view, registers default model and form scopes.
|
|
103
113
|
*/
|
|
104
114
|
constructor();
|
|
115
|
+
/** 获取/创建当前引擎的调度器(仅在本地作用域) */
|
|
116
|
+
getScheduler(): ModelOperationScheduler;
|
|
117
|
+
/** 释放并清理当前引擎本地调度器(若存在) */
|
|
118
|
+
disposeScheduler(): void;
|
|
119
|
+
/** 在目标模型生命周期达成时执行操作(仅在 View 引擎本地存储计划) */
|
|
120
|
+
scheduleModelOperation(fromModelOrUid: FlowModel | string, toUid: string, fn: (model: FlowModel) => Promise<void> | void, options?: ScheduleOptions): ScheduledCancel;
|
|
105
121
|
/** 上一个引擎(根引擎为 undefined) */
|
|
106
122
|
get previousEngine(): FlowEngine | undefined;
|
|
107
123
|
/** 下一个引擎(若存在) */
|
package/lib/flowEngine.js
CHANGED
|
@@ -59,6 +59,8 @@ var import_flowSettings = require("./flowSettings");
|
|
|
59
59
|
var import_models = require("./models");
|
|
60
60
|
var import_ReactView = require("./ReactView");
|
|
61
61
|
var import_resources = require("./resources");
|
|
62
|
+
var import_emitter = require("./emitter");
|
|
63
|
+
var import_ModelOperationScheduler = __toESM(require("./scheduler/ModelOperationScheduler"));
|
|
62
64
|
var import_utils = require("./utils");
|
|
63
65
|
var _FlowEngine_instances, registerModel_fn;
|
|
64
66
|
const _FlowEngine = class _FlowEngine {
|
|
@@ -118,6 +120,13 @@ const _FlowEngine = class _FlowEngine {
|
|
|
118
120
|
__publicField(this, "_previousEngine");
|
|
119
121
|
__publicField(this, "_nextEngine");
|
|
120
122
|
__publicField(this, "_resources", /* @__PURE__ */ new Map());
|
|
123
|
+
/**
|
|
124
|
+
* 引擎事件总线(目前用于模型生命周期等事件)。
|
|
125
|
+
* ViewScopedFlowEngine 持有自己的实例,实现作用域隔离。
|
|
126
|
+
*/
|
|
127
|
+
__publicField(this, "emitter", new import_emitter.Emitter());
|
|
128
|
+
/** 调度器:仅在 View 作用域引擎本地启用;根/Block 作用域默认不持有 */
|
|
129
|
+
__publicField(this, "_modelOperationScheduler");
|
|
121
130
|
__publicField(this, "logger");
|
|
122
131
|
/**
|
|
123
132
|
* Flow settings, including components and form scopes.
|
|
@@ -161,6 +170,27 @@ const _FlowEngine = class _FlowEngine {
|
|
|
161
170
|
});
|
|
162
171
|
this.executor = new import_FlowExecutor.FlowExecutor(this);
|
|
163
172
|
}
|
|
173
|
+
/** 获取/创建当前引擎的调度器(仅在本地作用域) */
|
|
174
|
+
getScheduler() {
|
|
175
|
+
if (!this._modelOperationScheduler) {
|
|
176
|
+
this._modelOperationScheduler = new import_ModelOperationScheduler.default(this);
|
|
177
|
+
}
|
|
178
|
+
return this._modelOperationScheduler;
|
|
179
|
+
}
|
|
180
|
+
/** 释放并清理当前引擎本地调度器(若存在) */
|
|
181
|
+
disposeScheduler() {
|
|
182
|
+
if (this._modelOperationScheduler) {
|
|
183
|
+
try {
|
|
184
|
+
this._modelOperationScheduler.dispose();
|
|
185
|
+
} finally {
|
|
186
|
+
this._modelOperationScheduler = void 0;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
/** 在目标模型生命周期达成时执行操作(仅在 View 引擎本地存储计划) */
|
|
191
|
+
scheduleModelOperation(fromModelOrUid, toUid, fn, options) {
|
|
192
|
+
return this.getScheduler().schedule(fromModelOrUid, toUid, fn, options);
|
|
193
|
+
}
|
|
164
194
|
/** 上一个引擎(根引擎为 undefined) */
|
|
165
195
|
get previousEngine() {
|
|
166
196
|
return this._previousEngine;
|
|
@@ -384,6 +414,10 @@ const _FlowEngine = class _FlowEngine {
|
|
|
384
414
|
}
|
|
385
415
|
this._modelInstances.set(modelInstance.uid, modelInstance);
|
|
386
416
|
modelInstance.onInit(options);
|
|
417
|
+
void this.emitter.emitAsync("model:created", {
|
|
418
|
+
uid: modelInstance.uid,
|
|
419
|
+
model: modelInstance
|
|
420
|
+
});
|
|
387
421
|
this._applyFlowDefinitionDefaultParams(modelInstance).catch((err) => {
|
|
388
422
|
console.warn("FlowEngine: apply flow defaultParams failed:", err);
|
|
389
423
|
});
|
|
@@ -485,6 +519,10 @@ const _FlowEngine = class _FlowEngine {
|
|
|
485
519
|
}
|
|
486
520
|
}
|
|
487
521
|
this._modelInstances.delete(uid);
|
|
522
|
+
void this.emitter.emitAsync("model:destroyed", {
|
|
523
|
+
uid,
|
|
524
|
+
model: modelInstance
|
|
525
|
+
});
|
|
488
526
|
return true;
|
|
489
527
|
}
|
|
490
528
|
/**
|
|
@@ -20,6 +20,7 @@ import { FlowDefinition } from '../FlowDefinition';
|
|
|
20
20
|
import { FlowSettingsOpenOptions } from '../flowSettings';
|
|
21
21
|
import type { EventDefinition, DispatchEventOptions } from '../types';
|
|
22
22
|
import { ForkFlowModel } from './forkFlowModel';
|
|
23
|
+
import type { ScheduleOptions } from '../scheduler/ModelOperationScheduler';
|
|
23
24
|
export declare enum ModelRenderMode {
|
|
24
25
|
ReactElement = "reactElement",
|
|
25
26
|
RenderFunction = "renderFunction"
|
|
@@ -91,6 +92,7 @@ export declare class FlowModel<Structure extends DefaultStructure = DefaultStruc
|
|
|
91
92
|
get subType(): "object" | "array";
|
|
92
93
|
get reactView(): import("../ReactView").ReactView;
|
|
93
94
|
get parentId(): string;
|
|
95
|
+
scheduleModelOperation(toUid: string, fn: (model: FlowModel) => Promise<void> | void, options?: ScheduleOptions): import("../scheduler/ModelOperationScheduler").ScheduledCancel;
|
|
94
96
|
static get meta(): FlowModelMeta;
|
|
95
97
|
static get globalFlowRegistry(): GlobalFlowRegistry;
|
|
96
98
|
protected static get actionRegistry(): ModelActionRegistry;
|
package/lib/models/flowModel.js
CHANGED
|
@@ -238,6 +238,10 @@ const _FlowModel = class _FlowModel {
|
|
|
238
238
|
get parentId() {
|
|
239
239
|
return this._options.parentId;
|
|
240
240
|
}
|
|
241
|
+
scheduleModelOperation(toUid, fn, options) {
|
|
242
|
+
var _a;
|
|
243
|
+
return (_a = this.flowEngine) == null ? void 0 : _a.scheduleModelOperation(this, toUid, fn, options);
|
|
244
|
+
}
|
|
241
245
|
static get meta() {
|
|
242
246
|
return modelMetas.get(this);
|
|
243
247
|
}
|
|
@@ -719,13 +723,23 @@ const _FlowModel = class _FlowModel {
|
|
|
719
723
|
modelInstance.props;
|
|
720
724
|
}
|
|
721
725
|
import_react.default.useEffect(() => {
|
|
726
|
+
var _a2, _b2;
|
|
722
727
|
if (typeof renderTarget.onMount === "function") {
|
|
723
728
|
renderTarget.onMount();
|
|
724
729
|
}
|
|
730
|
+
void ((_b2 = (_a2 = renderTarget.flowEngine) == null ? void 0 : _a2.emitter) == null ? void 0 : _b2.emitAsync("model:mounted", {
|
|
731
|
+
uid: renderTarget.uid,
|
|
732
|
+
model: renderTarget
|
|
733
|
+
}));
|
|
725
734
|
return () => {
|
|
735
|
+
var _a3, _b3;
|
|
726
736
|
if (typeof renderTarget.onUnmount === "function") {
|
|
727
737
|
renderTarget.onUnmount();
|
|
728
738
|
}
|
|
739
|
+
void ((_b3 = (_a3 = renderTarget.flowEngine) == null ? void 0 : _a3.emitter) == null ? void 0 : _b3.emitAsync("model:unmounted", {
|
|
740
|
+
uid: renderTarget.uid,
|
|
741
|
+
model: renderTarget
|
|
742
|
+
}));
|
|
729
743
|
};
|
|
730
744
|
}, [renderTarget]);
|
|
731
745
|
const isConfigMode = !!((_b = (_a = modelInstance == null ? void 0 : modelInstance.flowEngine) == null ? void 0 : _a.flowSettings) == null ? void 0 : _b.enabled);
|
|
@@ -0,0 +1,51 @@
|
|
|
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 type { FlowEngine } from '../flowEngine';
|
|
10
|
+
import type { FlowModel } from '../models/flowModel';
|
|
11
|
+
type LifecycleType = 'created' | 'mounted' | 'unmounted' | 'destroyed' | `event:${string}:start` | `event:${string}:end` | `event:${string}:error`;
|
|
12
|
+
export type ScheduleWhen = LifecycleType | ((e: LifecycleEvent) => boolean);
|
|
13
|
+
export interface ScheduleOptions {
|
|
14
|
+
when?: ScheduleWhen;
|
|
15
|
+
}
|
|
16
|
+
export type ScheduledCancel = () => boolean;
|
|
17
|
+
export interface LifecycleEvent {
|
|
18
|
+
type: LifecycleType;
|
|
19
|
+
uid: string;
|
|
20
|
+
model?: FlowModel;
|
|
21
|
+
runId?: string;
|
|
22
|
+
error?: any;
|
|
23
|
+
inputArgs?: Record<string, any>;
|
|
24
|
+
result?: any;
|
|
25
|
+
}
|
|
26
|
+
export declare class ModelOperationScheduler {
|
|
27
|
+
private engine;
|
|
28
|
+
private itemsByTargetUid;
|
|
29
|
+
private itemIdsByFromUid;
|
|
30
|
+
private itemsById;
|
|
31
|
+
private unbindHandlers;
|
|
32
|
+
private subscribedEventNames;
|
|
33
|
+
constructor(engine: FlowEngine);
|
|
34
|
+
/** 外部调用入口 */
|
|
35
|
+
schedule(fromModelOrUid: FlowModel | string, toUid: string, fn: (model: FlowModel) => Promise<void> | void, options?: ScheduleOptions): ScheduledCancel;
|
|
36
|
+
cancel(filter: {
|
|
37
|
+
fromUid?: string;
|
|
38
|
+
toUid?: string;
|
|
39
|
+
}): void;
|
|
40
|
+
/** 引擎关闭时释放 */
|
|
41
|
+
dispose(): void;
|
|
42
|
+
private bindEngineLifecycle;
|
|
43
|
+
private ensureEventSubscriptionIfNeeded;
|
|
44
|
+
private parseEventWhen;
|
|
45
|
+
private processLifecycleEvent;
|
|
46
|
+
private shouldTrigger;
|
|
47
|
+
private tryExecuteOnce;
|
|
48
|
+
private internalCancel;
|
|
49
|
+
private takeItem;
|
|
50
|
+
}
|
|
51
|
+
export default ModelOperationScheduler;
|
|
@@ -0,0 +1,262 @@
|
|
|
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 ModelOperationScheduler_exports = {};
|
|
29
|
+
__export(ModelOperationScheduler_exports, {
|
|
30
|
+
ModelOperationScheduler: () => ModelOperationScheduler,
|
|
31
|
+
default: () => ModelOperationScheduler_default
|
|
32
|
+
});
|
|
33
|
+
module.exports = __toCommonJS(ModelOperationScheduler_exports);
|
|
34
|
+
var import_secure = require("uid/secure");
|
|
35
|
+
const _ModelOperationScheduler = class _ModelOperationScheduler {
|
|
36
|
+
constructor(engine) {
|
|
37
|
+
this.engine = engine;
|
|
38
|
+
this.bindEngineLifecycle();
|
|
39
|
+
}
|
|
40
|
+
itemsByTargetUid = /* @__PURE__ */ new Map();
|
|
41
|
+
itemIdsByFromUid = /* @__PURE__ */ new Map();
|
|
42
|
+
itemsById = /* @__PURE__ */ new Map();
|
|
43
|
+
unbindHandlers = [];
|
|
44
|
+
subscribedEventNames = /* @__PURE__ */ new Set();
|
|
45
|
+
/** 外部调用入口 */
|
|
46
|
+
schedule(fromModelOrUid, toUid, fn, options) {
|
|
47
|
+
const fromUid = typeof fromModelOrUid === "string" ? fromModelOrUid : fromModelOrUid == null ? void 0 : fromModelOrUid.uid;
|
|
48
|
+
if (!fromUid) {
|
|
49
|
+
throw new Error("scheduleModelOperation: invalid from model or uid");
|
|
50
|
+
}
|
|
51
|
+
if (!toUid || typeof toUid !== "string") {
|
|
52
|
+
throw new Error("scheduleModelOperation: invalid target uid");
|
|
53
|
+
}
|
|
54
|
+
const item = {
|
|
55
|
+
id: (0, import_secure.uid)(),
|
|
56
|
+
fromUid,
|
|
57
|
+
toUid,
|
|
58
|
+
fn,
|
|
59
|
+
options: {
|
|
60
|
+
// 默认语义:仅当 when === 'created' 时,对“已存在的目标”进行一次立即执行;
|
|
61
|
+
// 其它 when 不做立即执行,需等待后续真实生命周期事件。
|
|
62
|
+
when: (options == null ? void 0 : options.when) ?? "created"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
let targetMap = this.itemsByTargetUid.get(toUid);
|
|
66
|
+
if (!targetMap) {
|
|
67
|
+
targetMap = /* @__PURE__ */ new Map();
|
|
68
|
+
this.itemsByTargetUid.set(toUid, targetMap);
|
|
69
|
+
}
|
|
70
|
+
targetMap.set(item.id, item);
|
|
71
|
+
let fromSet = this.itemIdsByFromUid.get(fromUid);
|
|
72
|
+
if (!fromSet) {
|
|
73
|
+
fromSet = /* @__PURE__ */ new Set();
|
|
74
|
+
this.itemIdsByFromUid.set(fromUid, fromSet);
|
|
75
|
+
}
|
|
76
|
+
fromSet.add(item.id);
|
|
77
|
+
this.itemsById.set(item.id, item);
|
|
78
|
+
this.ensureEventSubscriptionIfNeeded(item.options.when);
|
|
79
|
+
const modelNow = this.engine.getModel(toUid);
|
|
80
|
+
if (modelNow && item.options.when === "created") {
|
|
81
|
+
void this.tryExecuteOnce(item.id, { type: "created", uid: toUid, model: modelNow });
|
|
82
|
+
}
|
|
83
|
+
return () => this.internalCancel(item.id);
|
|
84
|
+
}
|
|
85
|
+
cancel(filter) {
|
|
86
|
+
const { fromUid, toUid } = filter || {};
|
|
87
|
+
if (toUid && this.itemsByTargetUid.has(toUid)) {
|
|
88
|
+
const map = this.itemsByTargetUid.get(toUid);
|
|
89
|
+
if (map)
|
|
90
|
+
for (const item of map.values()) {
|
|
91
|
+
if (fromUid && item.fromUid !== fromUid) continue;
|
|
92
|
+
this.internalCancel(item.id);
|
|
93
|
+
}
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (fromUid && this.itemIdsByFromUid.has(fromUid)) {
|
|
97
|
+
const set = this.itemIdsByFromUid.get(fromUid);
|
|
98
|
+
if (set)
|
|
99
|
+
for (const id of Array.from(set)) {
|
|
100
|
+
const it = this.itemsById.get(id);
|
|
101
|
+
if (!it) continue;
|
|
102
|
+
if (toUid && it.toUid !== toUid) continue;
|
|
103
|
+
this.internalCancel(id);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** 引擎关闭时释放 */
|
|
108
|
+
dispose() {
|
|
109
|
+
try {
|
|
110
|
+
for (const unbind of this.unbindHandlers) {
|
|
111
|
+
try {
|
|
112
|
+
unbind();
|
|
113
|
+
} catch (err) {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
this.unbindHandlers = [];
|
|
118
|
+
}
|
|
119
|
+
for (const map of this.itemsByTargetUid.values()) {
|
|
120
|
+
for (const item of map.values()) this.internalCancel(item.id);
|
|
121
|
+
}
|
|
122
|
+
this.itemsByTargetUid.clear();
|
|
123
|
+
this.itemIdsByFromUid.clear();
|
|
124
|
+
}
|
|
125
|
+
bindEngineLifecycle() {
|
|
126
|
+
const emitter = this.engine.emitter;
|
|
127
|
+
if (!emitter || typeof emitter.on !== "function") return;
|
|
128
|
+
const onCreated = /* @__PURE__ */ __name((e) => {
|
|
129
|
+
this.processLifecycleEvent(e.uid, { ...e, type: "created" });
|
|
130
|
+
}, "onCreated");
|
|
131
|
+
emitter.on("model:created", onCreated);
|
|
132
|
+
this.unbindHandlers.push(() => emitter.off("model:created", onCreated));
|
|
133
|
+
const onMounted = /* @__PURE__ */ __name((e) => {
|
|
134
|
+
this.processLifecycleEvent(e.uid, { ...e, type: "mounted" });
|
|
135
|
+
}, "onMounted");
|
|
136
|
+
emitter.on("model:mounted", onMounted);
|
|
137
|
+
this.unbindHandlers.push(() => emitter.off("model:mounted", onMounted));
|
|
138
|
+
const onGenericBeforeStart = /* @__PURE__ */ __name((e) => {
|
|
139
|
+
this.processLifecycleEvent(e.uid, { ...e, type: "event:beforeRender:start" });
|
|
140
|
+
}, "onGenericBeforeStart");
|
|
141
|
+
emitter.on("model:event:beforeRender:start", onGenericBeforeStart);
|
|
142
|
+
this.unbindHandlers.push(() => emitter.off("model:event:beforeRender:start", onGenericBeforeStart));
|
|
143
|
+
const onGenericBeforeEnd = /* @__PURE__ */ __name((e) => {
|
|
144
|
+
this.processLifecycleEvent(e.uid, { ...e, type: "event:beforeRender:end" });
|
|
145
|
+
}, "onGenericBeforeEnd");
|
|
146
|
+
emitter.on("model:event:beforeRender:end", onGenericBeforeEnd);
|
|
147
|
+
this.unbindHandlers.push(() => emitter.off("model:event:beforeRender:end", onGenericBeforeEnd));
|
|
148
|
+
const onUnmounted = /* @__PURE__ */ __name((e) => {
|
|
149
|
+
this.processLifecycleEvent(e.uid, { ...e, type: "unmounted" });
|
|
150
|
+
}, "onUnmounted");
|
|
151
|
+
emitter.on("model:unmounted", onUnmounted);
|
|
152
|
+
this.unbindHandlers.push(() => emitter.off("model:unmounted", onUnmounted));
|
|
153
|
+
const onDestroyed = /* @__PURE__ */ __name((e) => {
|
|
154
|
+
const targetBucket = this.itemsByTargetUid.get(e.uid);
|
|
155
|
+
const event = { ...e, type: "destroyed" };
|
|
156
|
+
if (targetBucket && targetBucket.size) {
|
|
157
|
+
const ids = Array.from(targetBucket.keys());
|
|
158
|
+
for (const id of ids) {
|
|
159
|
+
const it = this.itemsById.get(id);
|
|
160
|
+
if (!it) continue;
|
|
161
|
+
if (this.shouldTrigger(it.options.when, event)) {
|
|
162
|
+
void this.tryExecuteOnce(id, event);
|
|
163
|
+
} else {
|
|
164
|
+
this.internalCancel(id);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}, "onDestroyed");
|
|
169
|
+
emitter.on("model:destroyed", onDestroyed);
|
|
170
|
+
this.unbindHandlers.push(() => emitter.off("model:destroyed", onDestroyed));
|
|
171
|
+
}
|
|
172
|
+
ensureEventSubscriptionIfNeeded(when) {
|
|
173
|
+
if (!when || typeof when !== "string") return;
|
|
174
|
+
const parsed = this.parseEventWhen(when);
|
|
175
|
+
if (!parsed) return;
|
|
176
|
+
const { name } = parsed;
|
|
177
|
+
if (this.subscribedEventNames.has(name)) return;
|
|
178
|
+
this.subscribedEventNames.add(name);
|
|
179
|
+
const emitter = this.engine.emitter;
|
|
180
|
+
const onStart = /* @__PURE__ */ __name((e) => {
|
|
181
|
+
this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:start` });
|
|
182
|
+
}, "onStart");
|
|
183
|
+
const onEnd = /* @__PURE__ */ __name((e) => {
|
|
184
|
+
this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:end` });
|
|
185
|
+
}, "onEnd");
|
|
186
|
+
const onError = /* @__PURE__ */ __name((e) => {
|
|
187
|
+
this.processLifecycleEvent(e.uid, { ...e, type: `event:${name}:error` });
|
|
188
|
+
}, "onError");
|
|
189
|
+
emitter.on(`model:event:${name}:start`, onStart);
|
|
190
|
+
emitter.on(`model:event:${name}:end`, onEnd);
|
|
191
|
+
emitter.on(`model:event:${name}:error`, onError);
|
|
192
|
+
this.unbindHandlers.push(() => emitter.off(`model:event:${name}:start`, onStart));
|
|
193
|
+
this.unbindHandlers.push(() => emitter.off(`model:event:${name}:end`, onEnd));
|
|
194
|
+
this.unbindHandlers.push(() => emitter.off(`model:event:${name}:error`, onError));
|
|
195
|
+
}
|
|
196
|
+
parseEventWhen(when) {
|
|
197
|
+
if (!when || typeof when !== "string") return null;
|
|
198
|
+
const m = /^event:([^:]+):(start|end|error)$/.exec(when);
|
|
199
|
+
if (!m) return null;
|
|
200
|
+
return { name: m[1], phase: m[2] };
|
|
201
|
+
}
|
|
202
|
+
processLifecycleEvent(targetUid, event) {
|
|
203
|
+
const targetBucket = this.itemsByTargetUid.get(targetUid);
|
|
204
|
+
if (!targetBucket || targetBucket.size === 0) return;
|
|
205
|
+
const ids = Array.from(targetBucket.keys());
|
|
206
|
+
for (const id of ids) {
|
|
207
|
+
const item = this.itemsById.get(id);
|
|
208
|
+
if (!item) continue;
|
|
209
|
+
const should = this.shouldTrigger(item.options.when, event);
|
|
210
|
+
if (!should) continue;
|
|
211
|
+
void this.tryExecuteOnce(id, event);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
shouldTrigger(when, event) {
|
|
215
|
+
if (!when) return false;
|
|
216
|
+
if (typeof when === "function") return !!when(event);
|
|
217
|
+
return when === event.type;
|
|
218
|
+
}
|
|
219
|
+
async tryExecuteOnce(id, event) {
|
|
220
|
+
var _a, _b;
|
|
221
|
+
const item = this.takeItem(id);
|
|
222
|
+
if (!item) return;
|
|
223
|
+
try {
|
|
224
|
+
const model = event.model || this.engine.getModel(item.toUid);
|
|
225
|
+
if (!model) return;
|
|
226
|
+
await Promise.resolve(item.fn(model));
|
|
227
|
+
} catch (err) {
|
|
228
|
+
(_b = (_a = this.engine.logger) == null ? void 0 : _a.error) == null ? void 0 : _b.call(
|
|
229
|
+
_a,
|
|
230
|
+
{ err, id, fromUid: item.fromUid, toUid: item.toUid, when: item.options.when },
|
|
231
|
+
"ModelOperationScheduler: operation execution failed"
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
internalCancel(id) {
|
|
236
|
+
return !!this.takeItem(id);
|
|
237
|
+
}
|
|
238
|
+
takeItem(id) {
|
|
239
|
+
const it = this.itemsById.get(id);
|
|
240
|
+
if (!it) return void 0;
|
|
241
|
+
const { toUid, fromUid } = it;
|
|
242
|
+
const map = this.itemsByTargetUid.get(toUid);
|
|
243
|
+
if (map) {
|
|
244
|
+
map.delete(id);
|
|
245
|
+
if (map.size === 0) this.itemsByTargetUid.delete(toUid);
|
|
246
|
+
}
|
|
247
|
+
const set = this.itemIdsByFromUid.get(fromUid);
|
|
248
|
+
if (set) {
|
|
249
|
+
set.delete(id);
|
|
250
|
+
if (set.size === 0) this.itemIdsByFromUid.delete(fromUid);
|
|
251
|
+
}
|
|
252
|
+
this.itemsById.delete(id);
|
|
253
|
+
return it;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
__name(_ModelOperationScheduler, "ModelOperationScheduler");
|
|
257
|
+
let ModelOperationScheduler = _ModelOperationScheduler;
|
|
258
|
+
var ModelOperationScheduler_default = ModelOperationScheduler;
|
|
259
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
260
|
+
0 && (module.exports = {
|
|
261
|
+
ModelOperationScheduler
|
|
262
|
+
});
|
|
@@ -47,7 +47,7 @@ const DrawerComponent = React.forwardRef((props, ref) => {
|
|
|
47
47
|
const { children, footer: initialFooter, title, extra, hidden, ...drawerProps } = props;
|
|
48
48
|
const [open, setOpen] = React.useState(true);
|
|
49
49
|
const [footer, setFooter] = React.useState(() => initialFooter);
|
|
50
|
-
const [header, setHeader] = React.useState({ title, extra });
|
|
50
|
+
const [header, setHeader] = React.useState({ title, extra, ...drawerProps.header });
|
|
51
51
|
React.useImperativeHandle(
|
|
52
52
|
ref,
|
|
53
53
|
() => ({
|
package/lib/views/useDrawer.js
CHANGED
|
@@ -179,7 +179,7 @@ function useDrawer() {
|
|
|
179
179
|
ref: drawerRef,
|
|
180
180
|
...config,
|
|
181
181
|
footer: currentFooter,
|
|
182
|
-
header: currentHeader,
|
|
182
|
+
header: config.header || currentHeader,
|
|
183
183
|
hidden: (_d = (_c = config.inputArgs) == null ? void 0 : _c.hidden) == null ? void 0 : _d.value,
|
|
184
184
|
afterClose: () => {
|
|
185
185
|
var _a3;
|
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.35",
|
|
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.35",
|
|
12
|
+
"@nocobase/shared": "2.0.0-alpha.35",
|
|
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": "e60da9bab9fd4c8ba9457e9806d5428054cefa7a"
|
|
39
39
|
}
|
|
@@ -29,6 +29,14 @@ export function createViewScopedEngine(parent: FlowEngine): FlowEngine {
|
|
|
29
29
|
}
|
|
30
30
|
local.context.addDelegate(parent.context);
|
|
31
31
|
|
|
32
|
+
// 视图引擎关闭时,主动释放本地调度器以避免残留任务与引用
|
|
33
|
+
const originalUnlink = local.unlinkFromStack.bind(local);
|
|
34
|
+
local.unlinkFromStack = function () {
|
|
35
|
+
// 释放本地调度器与其事件监听,避免残留引用
|
|
36
|
+
local.disposeScheduler?.();
|
|
37
|
+
return originalUnlink();
|
|
38
|
+
};
|
|
39
|
+
|
|
32
40
|
// 默认全部代理到父引擎,只有少数字段(实例/缓存/执行器/上下文/链表指针)使用本地值
|
|
33
41
|
const localOnly = new Set<keyof FlowEngine | string>([
|
|
34
42
|
'_modelInstances',
|
|
@@ -37,6 +45,12 @@ export function createViewScopedEngine(parent: FlowEngine): FlowEngine {
|
|
|
37
45
|
'context',
|
|
38
46
|
'previousEngine',
|
|
39
47
|
'nextEngine',
|
|
48
|
+
// 调度器与事件总线局部化
|
|
49
|
+
'emitter',
|
|
50
|
+
'_modelOperationScheduler',
|
|
51
|
+
'getScheduler',
|
|
52
|
+
'scheduleModelOperation',
|
|
53
|
+
'disposeScheduler',
|
|
40
54
|
// 栈指针维护方法需要在本地执行,而非代理到父引擎
|
|
41
55
|
'unlinkFromStack',
|
|
42
56
|
'linkAfter',
|