2025-06-29 22:16:20 +08:00

529 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* This file is part of the NocoBase (R) project.
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
* Authors: NocoBase Team.
*
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { observable } from '@formily/reactive';
import { FlowSettings } from './flowSettings';
import { initFlowEngineLocale } from './locale';
import { FlowModel } from './models';
import { ReactView } from './ReactView';
import {
ActionDefinition,
ActionOptions,
CreateModelOptions,
FlowContext,
FlowDefinition,
IFlowModelRepository,
ModelConstructor,
} from './types';
import { isInheritedFrom } from './utils';
interface ApplyFlowCacheEntry {
status: 'pending' | 'resolved' | 'rejected';
promise: Promise<any>;
data?: any;
error?: any;
}
export class FlowEngine {
/** @private Stores registered action definitions. */
private actions: Map<string, ActionDefinition> = new Map();
/** @private Stores registered model constructors. */
private modelClasses: Map<string, ModelConstructor> = observable.shallow(new Map());
/** @private Stores created model instances. */
private modelInstances: Map<string, any> = new Map();
/** @public Stores flow settings including components and scopes for formily settings. */
public flowSettings: FlowSettings = new FlowSettings();
context: FlowContext['globals'] = {} as FlowContext['globals'];
private modelRepository: IFlowModelRepository | null = null;
private _applyFlowCache = new Map<string, ApplyFlowCacheEntry>();
/**
* 实验性 API用于在 FlowEngine 中集成 React 视图渲染能力。
* 该属性未来可能发生重大变更或被移除,请谨慎依赖。
* @experimental
*/
public reactView: ReactView;
constructor() {
this.reactView = new ReactView(this);
this.flowSettings.registerScopes({ t: this.translate.bind(this) });
}
/**
* 设置模型仓库,用于持久化和查询模型实例。
* 如果之前已设置过模型仓库,将会覆盖原有设置,并输出警告。
*
* @param modelRepository 要设置的模型仓库实例,实现 IFlowModelRepository 接口。
* @example
* flowEngine.setModelRepository(new MyFlowModelRepository());
*/
setModelRepository(modelRepository: IFlowModelRepository) {
if (this.modelRepository) {
console.warn('FlowEngine: Model repository is already set and will be overwritten.');
}
this.modelRepository = modelRepository;
}
setContext(context: any) {
this.context = { ...this.context, ...context };
if (this.context.i18n) {
initFlowEngineLocale(this.context.i18n);
}
}
getContext() {
return this.context;
}
/**
* 翻译函数,支持简单翻译和模板编译
* @param keyOrTemplate 翻译键或包含 {{t('key', options)}} 的模板字符串
* @param options 翻译选项(如命名空间、参数等)
* @returns 翻译后的文本
*
* @example
* // 简单翻译
* flowEngine.t('Hello World')
* flowEngine.t('Hello {name}', { name: 'John' })
*
* // 模板编译
* flowEngine.t("{{t('Hello World')}}")
* flowEngine.t("{{ t( 'User Name' ) }}")
* flowEngine.t("{{ t ( 'Email' , { ns: 'fields' } ) }}")
* flowEngine.t("前缀 {{ t('User Name') }} 后缀")
* flowEngine.t("{{t('Hello {name}', {name: 'John'})}}")
*/
public translate(keyOrTemplate: string, options?: any): string {
if (!keyOrTemplate || typeof keyOrTemplate !== 'string') {
return keyOrTemplate;
}
// 先尝试一次翻译
let result = this.translateKey(keyOrTemplate, options);
// 检查翻译结果是否包含模板语法,如果有则进行模板编译
if (this.isTemplate(result)) {
result = this.compileTemplate(result);
}
return result;
}
/**
* 内部翻译方法
* @private
*/
private translateKey(key: string, options?: any): string {
if (this.context?.i18n?.t) {
return this.context.i18n.t(key, options);
}
// 如果没有翻译函数,返回原始键值
return key;
}
/**
* 检查字符串是否包含模板语法
* @private
*/
private isTemplate(str: string): boolean {
return /\{\{\s*t\s*\(\s*["'`].*?["'`]\s*(?:,\s*.*?)?\s*\)\s*\}\}/g.test(str);
}
/**
* 编译模板字符串
* @private
*/
private compileTemplate(template: string): string {
return template.replace(
/\{\{\s*t\s*\(\s*["'`](.*?)["'`]\s*(?:,\s*((?:[^{}]|\{[^}]*\})*?))?\s*\)\s*\}\}/g,
(match, key, optionsStr) => {
try {
let templateOptions = {};
if (optionsStr) {
optionsStr = optionsStr.trim();
if (optionsStr.startsWith('{') && optionsStr.endsWith('}')) {
// 使用受限的 Function 构造器解析
try {
templateOptions = new Function('$root', `with($root) { return (${optionsStr}); }`)({});
} catch (parseError) {
return match;
}
}
}
return this.translateKey(key, templateOptions);
} catch (error) {
console.warn(`FlowEngine: Failed to compile template "${match}":`, error);
return match;
}
},
);
}
get applyFlowCache() {
return this._applyFlowCache;
}
registerActions(actions: Record<string, ActionDefinition>): void {
for (const [, definition] of Object.entries(actions)) {
this.registerAction(definition);
}
}
/**
* 注册一个 Action。支持泛型以确保正确的模型类型推导。
* Action 是流程中的可复用操作单元。
* @template TModel 具体的FlowModel子类类型
* @param {string | ActionDefinition<TModel>} nameOrDefinition Action 名称或 ActionDefinition 对象。
* 如果为字符串,则为 Action 名称,需要配合 options 参数。
* 如果为对象,则为完整的 ActionDefinition。
* @param {ActionOptions<TModel>} [options] 当第一个参数为 Action 名称时,此参数为 Action 的选项。
* @returns {void}
* @example
* // 方式一: 传入名称和选项
* flowEngine.registerAction<MyModel>('myAction', { handler: async (ctx, params) => { ... } });
* // 方式二: 传入 ActionDefinition 对象
* flowEngine.registerAction<MyModel>({ name: 'myAction', handler: async (ctx, params) => { ... } });
*/
public registerAction<TModel extends FlowModel = FlowModel>(
nameOrDefinition: string | ActionDefinition<TModel>,
options?: ActionOptions<TModel>,
): void {
let definition: ActionDefinition<TModel>;
if (typeof nameOrDefinition === 'string' && options) {
definition = {
...options,
name: nameOrDefinition,
};
} else if (typeof nameOrDefinition === 'object') {
definition = nameOrDefinition as ActionDefinition<TModel>;
} else {
throw new Error('Invalid arguments for registerAction');
}
if (this.actions.has(definition.name)) {
console.warn(`FlowEngine: Action with name '${definition.name}' is already registered and will be overwritten.`);
}
this.actions.set(definition.name, definition as ActionDefinition);
}
/**
* 获取已注册的 Action 定义。
* @template TModel 具体的FlowModel子类类型
* @param {string} name Action 名称。
* @returns {ActionDefinition<TModel> | undefined} Action 定义,如果未找到则返回 undefined。
*/
public getAction<TModel extends FlowModel = FlowModel>(name: string): ActionDefinition<TModel> | undefined {
return this.actions.get(name) as ActionDefinition<TModel> | undefined;
}
private registerModel(name: string, modelClass: ModelConstructor): void {
if (this.modelClasses.has(name)) {
console.warn(`FlowEngine: Model class with name '${name}' is already registered and will be overwritten.`);
}
this.modelClasses.set(name, modelClass);
}
/**
* 注册 Model 类。
* @param {Record<string, ModelConstructor>} models 要注册的 Model 类映射表,键为 Model 名称,值为 Model 构造函数。
* @returns {void}
* @example
* flowEngine.registerModels({
* 'UserModel': UserModel,
* 'OrderModel': OrderModel,
* 'ProductModel': ProductModel
* });
*/
public registerModels(models: Record<string, ModelConstructor>) {
for (const [name, modelClass] of Object.entries(models)) {
this.registerModel(name, modelClass);
}
}
getModelClasses() {
return this.modelClasses;
}
/**
* 获取已注册的 Model 类 (构造函数)。
* @param {string} name Model 类名称。
* @returns {ModelConstructor | undefined} Model 构造函数,如果未找到则返回 undefined。
*/
public getModelClass(name: string): ModelConstructor | undefined {
return this.modelClasses.get(name);
}
/**
* 根据条件查找已注册的 Model 类。
* @param predicate 回调函数,参数为 (name, ModelClass),返回 true 时即为命中。
* @returns [name, ModelConstructor] | undefined
*/
public findModelClass(
predicate: (name: string, ModelClass: ModelConstructor) => boolean,
): [string, ModelConstructor] | undefined {
for (const [name, ModelClass] of this.modelClasses) {
if (predicate(name, ModelClass)) {
return [name, ModelClass];
}
}
return undefined;
}
/**
* 根据父类过滤模型类(支持多层继承),可选自定义过滤器
* @param {string | ModelConstructor} baseClass 父类名称或构造函数
* @param {(ModelClass: ModelConstructor, className: string) => boolean} [filter] 过滤函数
* @returns {Map<string, ModelConstructor>} 继承自指定父类且通过过滤的模型类映射
*/
public getSubclassesOf(
baseClass: string | ModelConstructor,
filter?: (ModelClass: ModelConstructor, className: string) => boolean,
): Map<string, ModelConstructor> {
const parentModelClass = typeof baseClass === 'string' ? this.getModelClass(baseClass) : baseClass;
const result = new Map<string, ModelConstructor>();
if (!parentModelClass) return result;
for (const [className, ModelClass] of this.modelClasses) {
if (isInheritedFrom(ModelClass, parentModelClass)) {
if (!filter || filter(ModelClass, className)) {
result.set(className, ModelClass);
}
}
}
return result;
}
/**
* 创建并注册一个 Model 实例。
* 如果具有相同 UID 的实例已存在,则返回现有实例。
* @template T FlowModel 的子类型,默认为 FlowModel。
* @param {CreateModelOptions} options 创建模型的选项
* @returns {T} 创建的 Model 实例。
*/
public createModel<T extends FlowModel = FlowModel>(options: CreateModelOptions): T {
const { parentId, uid, use: modelClassName, subModels } = options;
const ModelClass = typeof modelClassName === 'string' ? this.getModelClass(modelClassName) : modelClassName;
if (!ModelClass) {
throw new Error(`Model class '${modelClassName}' not found. Please register it first.`);
}
if (uid && this.modelInstances.has(uid)) {
return this.modelInstances.get(uid) as T;
}
const modelInstance = new (ModelClass as ModelConstructor<T>)({ ...options, flowEngine: this } as any);
modelInstance.onInit(options);
if (parentId && this.modelInstances.has(parentId)) {
modelInstance.setParent(this.modelInstances.get(parentId));
}
this.modelInstances.set(modelInstance.uid, modelInstance);
return modelInstance;
}
/**
* 根据 UID 获取 Model 实例。
* @template T FlowModel 的子类型,默认为 FlowModel。
* @param {string} uid Model 实例的唯一标识符。
* @returns {T | undefined} Model 实例,如果未找到则返回 undefined。
*/
public getModel<T extends FlowModel = FlowModel>(uid: string): T | undefined {
return this.modelInstances.get(uid) as T | undefined;
}
forEachModel<T extends FlowModel = FlowModel>(callback: (model: T) => void): void {
this.modelInstances.forEach(callback);
}
/**
* 移除一个本地模型实例。
* @param {string} uid 要销毁的 Model 实例的唯一标识符。
* @returns {boolean} 如果成功销毁则返回 true否则返回 false (例如,实例不存在)。
*/
public removeModel(uid: string): boolean {
if (!this.modelInstances.has(uid)) {
console.warn(`FlowEngine: Model with UID '${uid}' does not exist.`);
return false;
}
const modelInstance = this.modelInstances.get(uid) as FlowModel;
modelInstance.clearForks();
// 从父模型中移除当前模型的引用
if (modelInstance.parent?.subModels) {
for (const subKey in modelInstance.parent.subModels) {
const subModelValue = modelInstance.parent.subModels[subKey];
if (Array.isArray(subModelValue)) {
const index = subModelValue.findIndex((subModel) => subModel.uid === modelInstance.uid);
if (index !== -1) {
subModelValue.splice(index, 1);
modelInstance.parent.emitter.emit('onSubModelRemoved', modelInstance);
break;
}
} else if (subModelValue && subModelValue.uid === modelInstance.uid) {
delete modelInstance.parent.subModels[subKey];
modelInstance.parent.emitter.emit('onSubModelRemoved', modelInstance);
break;
}
}
}
this.modelInstances.delete(uid);
return false;
}
private ensureModelRepository(): boolean {
if (!this.modelRepository) {
// 不抛错,直接返回 false
return false;
}
return true;
}
async loadModel<T extends FlowModel = FlowModel>(options): Promise<T | null> {
if (!this.ensureModelRepository()) return;
const data = await this.modelRepository.findOne(options);
return data?.uid ? this.createModel<T>(data as any) : null;
}
async loadOrCreateModel<T extends FlowModel = FlowModel>(options): Promise<T | null> {
if (!this.ensureModelRepository()) return;
const data = await this.modelRepository.findOne(options);
if (data?.uid) {
return this.createModel<T>(data as any);
} else {
const model = this.createModel<T>(options);
await model.save();
return model;
}
}
async saveModel<T extends FlowModel = FlowModel>(model: T) {
if (!this.ensureModelRepository()) return;
return await this.modelRepository.save(model);
}
async destroyModel(uid: string) {
if (this.ensureModelRepository()) {
await this.modelRepository.destroy(uid);
}
return this.removeModel(uid);
}
async moveModel(sourceId: any, targetId: any): Promise<void> {
const sourceModel = this.getModel(sourceId);
const targetModel = this.getModel(targetId);
if (!sourceModel || !targetModel) {
console.warn(`FlowEngine: Cannot move model. Source or target model not found.`);
return;
}
const move = (sourceModel: FlowModel, targetModel: FlowModel) => {
if (!sourceModel.parent || !targetModel.parent || sourceModel.parent !== targetModel.parent) {
console.error('FlowModel.moveTo: Both models must have the same parent to perform move operation.');
return false;
}
const subModels = sourceModel.parent.subModels[sourceModel.subKey];
if (!subModels || !Array.isArray(subModels)) {
console.error('FlowModel.moveTo: Parent subModels must be an array to perform move operation.');
return false;
}
const findIndex = (model: FlowModel) => subModels.findIndex((item) => item.uid === model.uid);
const currentIndex = findIndex(sourceModel);
const targetIndex = findIndex(targetModel);
if (currentIndex === -1 || targetIndex === -1) {
console.error('FlowModel.moveTo: Current or target model not found in parent subModels.');
return false;
}
if (currentIndex === targetIndex) {
console.warn('FlowModel.moveTo: Current model is already at the target position. No action taken.');
return false;
}
// 使用splice直接移动数组元素O(n)比排序O(n log n)更快)
const [movedModel] = subModels.splice(currentIndex, 1);
subModels.splice(targetIndex, 0, movedModel);
// 重新分配连续的sortIndex
subModels.forEach((model, index) => {
model.sortIndex = index;
});
return true;
};
move(sourceModel, targetModel);
if (this.ensureModelRepository()) {
const position = sourceModel.sortIndex - targetModel.sortIndex > 0 ? 'before' : 'after';
await this.modelRepository.move(sourceId, targetId, position);
}
// 触发事件以通知其他部分模型已移动
sourceModel.parent.emitter.emit('onSubModelMoved', { source: sourceModel, target: targetModel });
}
/**
* 注册一个流程 (Flow)。支持泛型以确保正确的模型类型推导。
* 流程是一系列步骤的定义,可以由事件触发或手动应用。
* @template TModel 具体的FlowModel子类类型
* @param {string} modelClassName 模型类名称。
* @param {FlowDefinition<TModel>} flowDefinition 流程定义。
* @returns {void}
*/
public registerFlow<TModel extends FlowModel = FlowModel>(
modelClassName: string,
flowDefinition: FlowDefinition<TModel>,
): void {
const ModelClass = this.getModelClass(modelClassName);
// 检查ModelClass是否存在
if (!ModelClass) {
console.warn(
`FlowEngine: Model class '${modelClassName}' not found. Flow '${flowDefinition.key}' will not be registered.`,
);
return;
}
if (typeof (ModelClass as any).registerFlow !== 'function') {
console.warn(
`FlowEngine: Model class '${modelClassName}' does not have a static registerFlow method. Flow '${flowDefinition.key}' will not be registered.`,
);
return;
}
(ModelClass as any).registerFlow(flowDefinition);
}
/**
* 根据父类过滤模型类(支持多层继承)
* @param {string | ModelConstructor} parentClass 父类名称或构造函数
* @returns {Map<string, ModelConstructor>} 继承自指定父类的模型类映射
*/
public filterModelClassByParent(parentClass: string | ModelConstructor) {
const parentModelClass = typeof parentClass === 'string' ? this.getModelClass(parentClass) : parentClass;
if (!parentModelClass) {
return new Map();
}
const modelClasses = new Map<string, ModelConstructor>();
for (const [className, ModelClass] of this.modelClasses) {
if (isInheritedFrom(ModelClass, parentModelClass)) {
modelClasses.set(className, ModelClass);
}
}
return modelClasses;
}
static generateApplyFlowCacheKey(prefix: string, flowKey: string, modelUid: string): string {
return `${prefix}:${flowKey}:${modelUid}`;
}
}