@nocobase/flow-engine
Version:
A standalone flow engine for NocoBase, managing workflows, models, and actions.
475 lines (431 loc) • 14.3 kB
text/typescript
/**
* 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 { ISchema } from '@formily/json-schema';
import { SubModelItem } from './components';
import { FlowContext, FlowModelContext, FlowRuntimeContext, FlowSettingsContext } from './flowContext';
import type { FlowEngine } from './flowEngine';
import type { FlowModel } from './models';
import { FilterGroupOptions } from './resources';
/**
* 工具类型:如果 T 是数组类型,则提取数组元素类型;否则返回 T 本身
* @template T 要检查的类型
* @example
* ```typescript
* type Test1 = ArrayElementType<string[]>; // string
* type Test2 = ArrayElementType<number[]>; // number
* type Test3 = ArrayElementType<string>; // string
* type Test4 = ArrayElementType<{ id: number }[]>; // { id: number }
* type Test5 = ArrayElementType<{ id: number }>; // { id: number }
* ```
*/
export type ArrayElementType<T> = T extends (infer U)[] ? U : T;
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? DeepPartial<U>[]
: T[P] extends Record<string, any>
? DeepPartial<T[P]>
: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
/**
* Defines a flow with generic model type support.
*/
export interface FlowDefinitionOptions<TModel extends FlowModel = FlowModel> {
/**
* Unique identifier for the flow.
* 建议采用统一的 xxxSettings 风格命名,例如:
* - pageSettings
* - tableSettings
* - cardSettings
* - formSettings
* - detailsSettings
* - buttonSettings
* - popupSettings
* - deleteSettings
* - datetimeSettings
* - numberSettings
* 这样命名便于识别和维护,建议全局统一。
* @example
* 'pageSettings'
* 'tableSettings'
* 'deleteSettings'
*/
key: string;
/**
* 人类可读的流标题,建议与 key 保持一致风格,采用 Xxx settings 命名,例如:
* - Page settings
* - Table settings
* - Card settings
* - Form settings
* - Details settings
* - Button settings
* - Popup settings
* - Delete settings
* - Datetime settings
* - Number settings
* 这样命名更清晰易懂,便于界面展示和团队协作。
* @example
* 'Page settings'
* 'Table settings'
* 'Delete settings'
*/
title?: string;
/**
* Whether this flow should be executed manually only (prevents auto-execution)
* Flows without 'on' property are auto-executed by default unless manual: true
*/
manual?: boolean;
/**
* Sort order for flow execution, lower numbers execute first
* Defaults to 0, can be negative
*/
sort?: number;
/**
* 允许该 Flow 被 `dispatchEvent` 触发的事件配置。
* 仅用于声明触发事件名(字符串或 { eventName }),不包含处理函数。
*/
on?: FlowEvent<TModel>;
steps: Record<string, StepDefinition<TModel>>;
/**
* Flow 级默认参数:在模型实例化(createModel)时,为“当前 Flow”的步骤参数填充初始值。
* 仅填补缺失,不覆盖已有。固定返回形状:{ [stepKey]: params }
*/
defaultParams?: Record<string, any> | ((ctx: FlowModelContext) => StepParam | Promise<StepParam>);
}
export interface IModelComponentProps {
[key: string]: any;
}
// 定义只读版本的props类型
export type ReadonlyModelProps = Readonly<IModelComponentProps>;
export type CreateSubModelOptions = CreateModelOptions | FlowModel;
/**
* Constructor for model classes.
*/
export type ModelConstructor<T extends FlowModel<any> = FlowModel<any>> = (new (
options: FlowModelOptions<any>,
) => T) & {
meta?: FlowModelMeta;
};
export enum ActionScene {
/** 块级联动规则可用 */
BLOCK_LINKAGE_RULES = 1,
/** 表单字段级联动规则可用 */
FIELD_LINKAGE_RULES,
/** 详情字段级联动规则可用 */
DETAILS_FIELD_LINKAGE_RULES,
/** 按钮级联动规则可用 */
ACTION_LINKAGE_RULES,
/** 动态事件流可用 */
DYNAMIC_EVENT_FLOW,
}
/**
* Defines a reusable action with generic model type support.
*/
export interface ActionDefinition<TModel extends FlowModel = FlowModel, TCtx extends FlowContext = FlowContext> {
name: string; // Unique identifier for the action
title?: string;
handler: (ctx: TCtx, params: any) => Promise<any> | any;
uiSchema?: Record<string, ISchema> | ((ctx: TCtx) => Record<string, ISchema> | Promise<Record<string, ISchema>>);
defaultParams?: Record<string, any> | ((ctx: TCtx) => Record<string, any> | Promise<Record<string, any>>);
beforeParamsSave?: (ctx: FlowSettingsContext<TModel>, params: any, previousParams: any) => void | Promise<void>;
afterParamsSave?: (ctx: FlowSettingsContext<TModel>, params: any, previousParams: any) => void | Promise<void>;
useRawParams?: boolean | ((ctx: TCtx) => boolean | Promise<boolean>);
uiMode?: StepUIMode | ((ctx: FlowRuntimeContext<TModel>) => StepUIMode | Promise<StepUIMode>);
scene?: ActionScene | ActionScene[];
sort?: number;
}
/**
* Flow 事件名称集合。
* - 收录内置常用事件,便于智能提示;
* - 允许扩展字符串以保持向后兼容。
*/
export type FlowEventName =
| 'click'
| 'submit'
| 'reset'
| 'remove'
| 'openView'
| 'dropdownOpen'
| 'popupScroll'
| 'search'
| 'customRequest'
| 'collapseToggle'
// fallback to any string for extensibility
| (string & {});
/**
* Flow 事件类型(供 FlowDefinitionOptions.on 使用)。
*/
export type FlowEvent<TModel extends FlowModel = FlowModel> =
| FlowEventName
| { eventName: FlowEventName; defaultParams?: Record<string, any> };
/**
* 事件定义:用于事件注册表(全局/模型类级)。
*/
export type EventDefinition<
TModel extends FlowModel = FlowModel,
TCtx extends FlowContext = FlowContext,
> = ActionDefinition<TModel, TCtx>;
export type StepUIMode =
| 'dialog'
| 'drawer'
| 'embed'
// | 'switch'
// | 'select'
| { type?: 'dialog' | 'drawer' | 'embed'; props?: Record<string, any> };
// | { type: 'switch'; props?: Record<string, any> }
// | { type: 'select'; props?: Record<string, any> }
/**
* Step definition with unified support for both registered actions and inline handlers
* Extends ActionDefinition but makes some properties optional and adds step-specific properties
*/
export interface StepDefinition<TModel extends FlowModel = FlowModel>
extends Partial<Omit<ActionDefinition<TModel, FlowRuntimeContext<TModel>>, 'name'>> {
key?: string; // Unique identifier for the step within the flow
// Step-specific properties
isAwait?: boolean; // Whether to await the handler, defaults to true
use?: string; // Name of the registered ActionDefinition to use as base
sort?: number; // Sort order for step execution, lower numbers execute first
// Step configuration
// `preset: true` 的 step params 需要在创建时填写,没有标记的可以创建模型后再填写。
preset?: boolean;
paramsRequired?: boolean; // Optional: whether the step params are required, will open the config dialog before adding the model
hideInSettings?: boolean; // Optional: whether to hide the step in the settings menu
uiMode?: StepUIMode | ((ctx: FlowRuntimeContext<TModel>) => StepUIMode | Promise<StepUIMode>);
}
/**
* Action options for registering actions with generic model type support
*/
export interface ActionOptions<TModel extends FlowModel = FlowModel, P = any, R = any> {
name: string; // Unique identifier for the action
handler: (ctx: FlowRuntimeContext<TModel>, params: P) => Promise<R> | R;
uiSchema?:
| Record<string, ISchema>
| ((ctx: FlowRuntimeContext<TModel>) => Record<string, ISchema> | Promise<Record<string, ISchema>>);
defaultParams?: Partial<P> | ((ctx: FlowRuntimeContext<TModel>) => Partial<P> | Promise<Partial<P>>);
}
/**
* Steps parameters structure for flow models
*
* @example
* ```typescript
* const stepParams: StepParams = {
* 'flow1': {
* 'step1': {
* 'param1': 'value1',
* 'param2': 'value2'
* },
* 'step2': {
* 'param3': 'value3'
* }
* },
* 'flow2': {
* 'step1': {
* 'param1': 'value1'
* }
* }
* }
* ```
*/
type StepParam = {
[stepKey: string]: {
[paramKey: string]: any;
};
};
export type StepParams = {
[flowKey: string]: StepParam;
};
export type ParamObject = {
[key: string]: unknown;
[key: number]: never;
};
/**
* 已注册模型的类名
*/
export type RegisteredModelClassName = string;
/**
* Options for creating a model instance
*/
export interface CreateModelOptions {
uid?: string;
use: RegisteredModelClassName | ModelConstructor;
props?: IModelComponentProps;
flowRegistry?: Record<string, Omit<FlowDefinitionOptions, 'key'>>;
stepParams?: StepParams;
subModels?: Record<string, CreateSubModelOptions | CreateSubModelOptions[]>;
parentId?: string;
subKey?: string;
subType?: 'object' | 'array';
sortIndex?: number; // 排序索引
delegateToParent?: boolean;
[key: string]: any; // 允许额外的自定义选项
}
export interface IFlowModelRepository<T extends FlowModel = FlowModel> {
findOne(query: Record<string, any>): Promise<Record<string, any> | null>;
save(model: T, options?: { onlyStepParams?: boolean }): Promise<Record<string, any>>;
destroy(uid: string): Promise<boolean>;
move(sourceId: string, targetId: string, position: 'before' | 'after'): Promise<void>;
}
/**
* 步骤设置对话框的属性接口
*/
export interface StepSettingsDialogProps {
model: FlowModel;
flowKey: string;
stepKey: string;
dialogWidth?: number | string;
dialogTitle?: string;
mode?: 'dialog' | 'drawer'; // 设置模式,默认为'dialog'
ctx?: FlowRuntimeContext;
uiModeProps?: Record<string, any>;
cleanup?: () => void;
}
/**
* 步骤设置抽屉的属性接口
*/
export interface StepSettingsDrawerProps {
model: FlowModel;
flowKey: string;
stepKey: string;
drawerWidth?: number | string;
drawerTitle?: string;
ctx?: FlowRuntimeContext;
uiModeProps?: Record<string, any>;
cleanup?: () => void;
}
/**
* 统一的步骤设置属性接口
*/
export interface StepSettingsProps {
model: FlowModel;
flowKey: string;
stepKey: string;
width?: number | string;
title?: string;
}
/**
* 分步表单对话框的属性接口
*/
export interface RequiredConfigStepFormDialogProps {
model: FlowModel;
dialogWidth?: number | string;
dialogTitle?: string;
}
export type SubModelValue<TModel extends FlowModel = FlowModel> = TModel | TModel[];
export interface DefaultStructure {
parent?: FlowModel;
subModels?: Record<string, FlowModel | FlowModel[]>;
}
/**
* 提取Structure中parent的类型,如果没有定义则使用FlowModel | null
*/
export type ParentFlowModel<Structure> = Structure extends { parent: infer P } ? P : FlowModel | null;
/**
* Options for FlowModel constructor
*/
export interface FlowModelOptions<Structure extends { parent?: FlowModel; subModels?: any } = DefaultStructure> {
uid?: string;
use?: string;
async?: boolean; // 是否异步加载模型
props?: IModelComponentProps; // 组件属性
stepParams?: StepParams;
subModels?: Structure['subModels'];
flowRegistry?: Record<string, Omit<FlowDefinitionOptions, 'key'>>;
flowEngine?: FlowEngine;
parentId?: string;
delegateToParent?: boolean;
subKey?: string;
subType?: 'object' | 'array';
sortIndex?: number;
/**
* 是否启用“始终干净”的运行模式:
* - false:与传统模式一致(直接在 master 或当前 fork 上执行)。
*/
cleanRun?: boolean;
}
export type FlowModelMeta =
// 从 SubModelItem 选取的属性,保持原始类型
Pick<
SubModelItem,
| 'key'
| 'label'
| 'icon'
| 'children'
| 'useModel'
| 'createModelOptions'
| 'toggleable'
| 'searchable'
| 'searchPlaceholder'
> & {
// FlowModelMeta 独有的属性
group?: string;
/**
* 菜单展示形态:
* - 'group':作为分组标题,子项平铺显示(默认)
* - 'submenu':作为可点击的一级项,展开二级子菜单
*/
menuType?: 'group' | 'submenu';
/**
* 排序权重,数字越小排序越靠前,用于控制显示顺序和默认选择
* 排序最靠前的将作为默认选择
* @default 0
*/
sort?: number;
/**
* 是否在菜单中隐藏该模型类
* @default false
*/
hide?: boolean;
eventList?: { label: string; value: string }[]; // 支持的事件列表
};
/**
* 字段 FlowModel 的专用元数据接口
* 继承自 FlowModelMeta,添加了字段接口相关的属性
*/
export interface FieldFlowModelMeta extends FlowModelMeta {
/**
* 支持的字段接口组列表,基于 CollectionFieldInterface 的 group 属性
* 如:['basic', 'choices', 'relation'] 等
* 如果不指定,则支持所有接口(向后兼容)
*/
supportedInterfaceGroups?: string[];
/**
* 支持的具体接口列表(可选,用于更精确的控制)
* 如:['input', 'textarea', 'select'] 等
*/
supportedInterfaces?: string[];
}
export type { ForkFlowModel } from './models';
/**
* 工具栏项目配置接口
*/
export interface ToolbarItemConfig {
/** 项目的唯一标识 */
key: string;
/** 项目组件,接收 model 作为 props,内部处理所有逻辑 */
component: React.ComponentType<{ model: FlowModel; [key: string]: any }>;
/** 是否显示项目的条件函数 */
visible?: (model: FlowModel) => boolean;
/** 排序权重,数字越小越靠右(先添加的在右边) */
sort?: number;
}
export interface ApplyFlowCacheEntry {
status: 'pending' | 'resolved' | 'rejected';
promise: Promise<any>;
data?: any;
error?: any;
}
export interface PersistOptions {
/**
* 是否持久化(保存到数据库),默认为 true
*/
persist?: boolean;
}
export type ResourceType<T = any> = string | { new (...args: any[]): T };