mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-07-02 03:02:19 +08:00
feat: enhance FlowModel and FlowContext with generic type support
This commit is contained in:
parent
5b70b68383
commit
f3353b1a4e
@ -1,4 +1,3 @@
|
||||
import * as icons from '@ant-design/icons';
|
||||
import { Plugin } from '@nocobase/client';
|
||||
import { defineAction, defineFlow, FlowModel, FlowModelRenderer } from '@nocobase/flow-engine';
|
||||
import { Button } from 'antd';
|
||||
|
@ -38,7 +38,10 @@ const modelMetas = new WeakMap<typeof FlowModel, FlowModelMeta>();
|
||||
// 使用WeakMap存储每个类的flows
|
||||
const modelFlows = new WeakMap<typeof FlowModel, Map<string, FlowDefinition>>();
|
||||
|
||||
export class FlowModel<Structure extends { parent?: any; subModels?: any } = DefaultStructure> {
|
||||
export class FlowModel<
|
||||
TFlowContext extends FlowContext = FlowContext,
|
||||
Structure extends { parent?: any; subModels?: any } = DefaultStructure,
|
||||
> {
|
||||
public readonly uid: string;
|
||||
public sortIndex: number;
|
||||
public props: IModelComponentProps = {};
|
||||
@ -141,12 +144,15 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
* @param {FlowDefinition<TModel>} [flowDefinition] 当第一个参数为流程 Key 时,此参数为流程的定义。
|
||||
* @returns {void}
|
||||
*/
|
||||
public static registerFlow<TModel extends new (...args: any[]) => FlowModel<any>>(
|
||||
this: TModel,
|
||||
keyOrDefinition: string | FlowDefinition<InstanceType<TModel>>,
|
||||
flowDefinition?: Omit<FlowDefinition<InstanceType<TModel>>, 'key'> & { key?: string },
|
||||
public static registerFlow<
|
||||
TFlowContext extends FlowContext = FlowContext,
|
||||
TStructure extends { parent?: any; subModels?: any } = DefaultStructure,
|
||||
>(
|
||||
this: FlowModel<TFlowContext, TStructure>,
|
||||
keyOrDefinition: string | FlowDefinition<TFlowContext>,
|
||||
flowDefinition?: Omit<FlowDefinition<TFlowContext>, 'key'> & { key?: string },
|
||||
): void {
|
||||
let definition: FlowDefinition<InstanceType<TModel>>;
|
||||
let definition: FlowDefinition<TFlowContext>;
|
||||
let key: string;
|
||||
|
||||
if (typeof keyOrDefinition === 'string' && flowDefinition) {
|
||||
@ -184,7 +190,11 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
* @param {Omit<ExtendedFlowDefinition, 'key'>} [extendDefinition] 当第一个参数为流程 Key 时,此参数为流程的扩展定义。
|
||||
* @returns {void}
|
||||
*/
|
||||
public static extendFlow<TModel extends FlowModel = FlowModel>(
|
||||
public static extendFlow<
|
||||
TFlowContext extends FlowContext = FlowContext,
|
||||
TStructure extends { parent?: any; subModels?: any } = DefaultStructure,
|
||||
>(
|
||||
this: FlowModel<TFlowContext, TStructure>,
|
||||
keyOrDefinition: string | ExtendedFlowDefinition,
|
||||
extendDefinition?: Omit<ExtendedFlowDefinition, 'key'>,
|
||||
): void {
|
||||
@ -214,7 +224,7 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
);
|
||||
// 移除patch标记,作为新流程注册
|
||||
const { patch, ...newFlowDef } = definition;
|
||||
this.registerFlow(newFlowDef as FlowDefinition<TModel>);
|
||||
this.registerFlow(newFlowDef as FlowDefinition<TFlowContext>);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -222,7 +232,7 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
const mergedFlow = mergeFlowDefinitions(originalFlow, definition);
|
||||
|
||||
// 注册合并后的流程
|
||||
this.registerFlow(mergedFlow as FlowDefinition<TModel>);
|
||||
this.registerFlow(mergedFlow as FlowDefinition<TFlowContext>);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -367,7 +377,7 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
};
|
||||
|
||||
const globalContexts = currentFlowEngine.getContext() || {};
|
||||
const flowContext: FlowContext<this> = {
|
||||
const flowContext: TFlowContext = {
|
||||
exit: () => {
|
||||
throw new FlowExitException(flowKey, this.uid);
|
||||
},
|
||||
@ -383,12 +393,12 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
extra: extra || {},
|
||||
model: this,
|
||||
app: globalContexts.app || {},
|
||||
};
|
||||
} as any;
|
||||
|
||||
for (const stepKey in flow.steps) {
|
||||
if (Object.prototype.hasOwnProperty.call(flow.steps, stepKey)) {
|
||||
const step: StepDefinition = flow.steps[stepKey];
|
||||
let handler: ((ctx: FlowContext<this>, params: any) => Promise<any> | any) | undefined;
|
||||
let handler: ((ctx: TFlowContext, params: any) => Promise<any> | any) | undefined;
|
||||
let combinedParams: Record<string, any> = {};
|
||||
let actionDefinition;
|
||||
|
||||
@ -478,7 +488,7 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
* @returns 新创建的 FlowModel 子类
|
||||
*/
|
||||
public static extends<T extends typeof FlowModel>(this: T, flows: ExtendedFlowDefinition[] = []): T {
|
||||
class CustomFlowModel extends (this as unknown as typeof FlowModel) {
|
||||
class CustomFlowModel extends (this as unknown as typeof FlowModel<FlowContext>) {
|
||||
// @ts-ignore
|
||||
static name = `CustomFlowModel_${generateUid()}`;
|
||||
}
|
||||
@ -552,7 +562,7 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
return <div {...this.props}></div>;
|
||||
}
|
||||
|
||||
setParent(parent: FlowModel): void {
|
||||
setParent(parent: FlowModel<FlowContext>): void {
|
||||
if (!parent || !(parent instanceof FlowModel)) {
|
||||
throw new Error('Parent must be an instance of FlowModel.');
|
||||
}
|
||||
@ -560,8 +570,8 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
this._options.parentId = parent.uid;
|
||||
}
|
||||
|
||||
addSubModel(subKey: string, options: CreateModelOptions | FlowModel) {
|
||||
let model: FlowModel;
|
||||
addSubModel(subKey: string, options: CreateModelOptions | FlowModel<FlowContext>) {
|
||||
let model: FlowModel<FlowContext>;
|
||||
if (options instanceof FlowModel) {
|
||||
if (options.parent && options.parent !== this) {
|
||||
throw new Error('Sub model already has a parent.');
|
||||
@ -578,8 +588,8 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
return model;
|
||||
}
|
||||
|
||||
setSubModel(subKey: string, options: CreateModelOptions | FlowModel) {
|
||||
let model: FlowModel;
|
||||
setSubModel(subKey: string, options: CreateModelOptions | FlowModel<FlowContext>) {
|
||||
let model: FlowModel<FlowContext>;
|
||||
if (options instanceof FlowModel) {
|
||||
if (options.parent && options.parent !== this) {
|
||||
throw new Error('Sub model already has a parent.');
|
||||
@ -771,7 +781,7 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
for (const subModelKey in this.subModels) {
|
||||
data.subModels = data.subModels || {};
|
||||
if (Array.isArray(this.subModels[subModelKey])) {
|
||||
data.subModels[subModelKey] = this.subModels[subModelKey].map((model: FlowModel, index) => ({
|
||||
data.subModels[subModelKey] = this.subModels[subModelKey].map((model: FlowModel<FlowContext>, index) => ({
|
||||
...model.serialize(),
|
||||
sortIndex: index,
|
||||
}));
|
||||
@ -783,6 +793,8 @@ export class FlowModel<Structure extends { parent?: any; subModels?: any } = Def
|
||||
}
|
||||
}
|
||||
|
||||
export function defineFlow<TModel extends FlowModel = FlowModel>(definition: FlowDefinition): FlowDefinition<TModel> {
|
||||
export function defineFlow<TModel extends FlowModel<FlowContext> = FlowModel<FlowContext>>(
|
||||
definition: FlowDefinition,
|
||||
): FlowDefinition<TModel> {
|
||||
return definition as FlowDefinition<TModel>;
|
||||
}
|
||||
|
42
packages/core/flow-engine/src/models/typeDemo.tsx
Normal file
42
packages/core/flow-engine/src/models/typeDemo.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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 { FlowContext } from '../types';
|
||||
import { defineAction } from '../utils';
|
||||
import { FlowModel } from './flowModel';
|
||||
|
||||
class TypeDemoModel extends FlowModel<FlowContext<TypeDemoModel, { abc: string }, { def: string }, { ghi: string }>> {
|
||||
injectedValue: {
|
||||
abc: string;
|
||||
};
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const demoAction = defineAction<FlowContext<TypeDemoModel, { abc: string }, { def: string }, { ghi: string }>>({
|
||||
name: 'demoAction',
|
||||
title: 'Demo Action',
|
||||
uiSchema: {},
|
||||
handler(ctx, params) {},
|
||||
});
|
||||
|
||||
TypeDemoModel.registerFlow({
|
||||
key: 'typeDemo',
|
||||
title: 'Type Demo',
|
||||
on: {
|
||||
eventName: 'click',
|
||||
},
|
||||
steps: {
|
||||
step1: {
|
||||
handler(ctx) {},
|
||||
},
|
||||
},
|
||||
});
|
@ -38,7 +38,7 @@ export type DeepPartial<T> = {
|
||||
/**
|
||||
* Defines a flow with generic model type support.
|
||||
*/
|
||||
export interface FlowDefinition<TModel extends FlowModel = FlowModel> {
|
||||
export interface FlowDefinition<TFlowContext extends FlowContext = FlowContext> {
|
||||
key: string; // Unique identifier for the flow
|
||||
title?: string;
|
||||
/**
|
||||
@ -56,7 +56,7 @@ export interface FlowDefinition<TModel extends FlowModel = FlowModel> {
|
||||
on?: {
|
||||
eventName: string;
|
||||
};
|
||||
steps: Record<string, StepDefinition<TModel>>;
|
||||
steps: Record<string, StepDefinition<TFlowContext>>;
|
||||
}
|
||||
|
||||
// 扩展FlowDefinition类型,添加partial标记用于部分覆盖
|
||||
@ -87,7 +87,12 @@ export type ReadonlyModelProps = Readonly<IModelComponentProps>;
|
||||
/**
|
||||
* Context object passed to handlers during flow execution.
|
||||
*/
|
||||
export interface FlowContext<TModel extends FlowModel = FlowModel> {
|
||||
export interface FlowContext<
|
||||
TModel extends FlowModel<FlowContext> = FlowModel<any>,
|
||||
TExtra extends FlowExtraContext = FlowExtraContext,
|
||||
TShared extends Record<string, any> = Record<string, any>,
|
||||
TGlobals extends Record<string, any> = Record<string, any>,
|
||||
> {
|
||||
exit: () => void; // Terminate the entire flow execution
|
||||
logger: {
|
||||
info: (message: string, meta?: any) => void;
|
||||
@ -96,19 +101,19 @@ export interface FlowContext<TModel extends FlowModel = FlowModel> {
|
||||
debug: (message: string, meta?: any) => void;
|
||||
};
|
||||
stepResults: Record<string, any>; // Results from previous steps
|
||||
shared: Record<string, any>; // Shared data within the flow (read/write)
|
||||
globals: Record<string, any>; // Global context data (read-only)
|
||||
extra: Record<string, any>; // Extra context passed to applyFlow (read-only)
|
||||
shared: TShared; // Shared data within the flow (read/write)
|
||||
globals: TGlobals; // Global context data (read-only)
|
||||
extra: TExtra; // Extra context passed to applyFlow (read-only)
|
||||
model: TModel; // Current model instance with specific type
|
||||
app: any; // Application instance (required)
|
||||
}
|
||||
|
||||
export type CreateSubModelOptions = CreateModelOptions | FlowModel;
|
||||
export type CreateSubModelOptions = CreateModelOptions | FlowModel<FlowContext>;
|
||||
|
||||
/**
|
||||
* Constructor for model classes.
|
||||
*/
|
||||
export type ModelConstructor<T extends FlowModel = FlowModel> = new (options: {
|
||||
export type ModelConstructor<T extends FlowModel<FlowContext> = FlowModel<FlowContext>> = new (options: {
|
||||
uid: string;
|
||||
props?: IModelComponentProps;
|
||||
stepParams?: StepParams;
|
||||
@ -120,20 +125,18 @@ export type ModelConstructor<T extends FlowModel = FlowModel> = new (options: {
|
||||
/**
|
||||
* Defines a reusable action with generic model type support.
|
||||
*/
|
||||
export interface ActionDefinition<TModel extends FlowModel = FlowModel> {
|
||||
export interface ActionDefinition<TFlowContext extends FlowContext = FlowContext> {
|
||||
name: string; // Unique identifier for the action
|
||||
title?: string;
|
||||
handler: (ctx: FlowContext<TModel>, params: any) => Promise<any> | any;
|
||||
handler: (ctx: TFlowContext, params: any) => Promise<any> | any;
|
||||
uiSchema?: Record<string, ISchema>;
|
||||
defaultParams?:
|
||||
| Record<string, any>
|
||||
| ((ctx: ParamsContext<TModel>) => Record<string, any> | Promise<Record<string, any>>);
|
||||
defaultParams?: Record<string, any> | ((ctx: TFlowContext) => Record<string, any> | Promise<Record<string, any>>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base interface for a step definition with generic model type support.
|
||||
*/
|
||||
interface BaseStepDefinition<TModel extends FlowModel = FlowModel> {
|
||||
interface BaseStepDefinition<TFlowContext extends FlowContext = FlowContext> {
|
||||
title?: string;
|
||||
isAwait?: boolean; // Whether to await the handler, defaults to true
|
||||
}
|
||||
@ -141,12 +144,11 @@ interface BaseStepDefinition<TModel extends FlowModel = FlowModel> {
|
||||
/**
|
||||
* Step that uses a registered Action with generic model type support.
|
||||
*/
|
||||
export interface ActionStepDefinition<TModel extends FlowModel = FlowModel> extends BaseStepDefinition<TModel> {
|
||||
export interface ActionStepDefinition<TFlowContext extends FlowContext = FlowContext>
|
||||
extends BaseStepDefinition<TFlowContext> {
|
||||
use: string; // Name of the registered ActionDefinition
|
||||
uiSchema?: Record<string, ISchema>; // Optional: overrides uiSchema from ActionDefinition
|
||||
defaultParams?:
|
||||
| Record<string, any>
|
||||
| ((ctx: ParamsContext<TModel>) => Record<string, any> | Promise<Record<string, any>>); // Optional: overrides/extends defaultParams from ActionDefinition
|
||||
defaultParams?: Record<string, any> | ((ctx: TFlowContext) => Record<string, any> | Promise<Record<string, any>>); // Optional: overrides/extends defaultParams from ActionDefinition
|
||||
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
|
||||
// Cannot have its own handler
|
||||
@ -156,21 +158,20 @@ export interface ActionStepDefinition<TModel extends FlowModel = FlowModel> exte
|
||||
/**
|
||||
* Step that defines its handler inline with generic model type support.
|
||||
*/
|
||||
export interface InlineStepDefinition<TModel extends FlowModel = FlowModel> extends BaseStepDefinition<TModel> {
|
||||
handler: (ctx: FlowContext<TModel>, params: any) => Promise<any> | any;
|
||||
export interface InlineStepDefinition<TFlowContext extends FlowContext = FlowContext>
|
||||
extends BaseStepDefinition<TFlowContext> {
|
||||
handler: (ctx: TFlowContext, params: any) => Promise<any> | any;
|
||||
uiSchema?: Record<string, ISchema>; // Optional: uiSchema for this inline step
|
||||
defaultParams?:
|
||||
| Record<string, any>
|
||||
| ((ctx: ParamsContext<TModel>) => Record<string, any> | Promise<Record<string, any>>); // Optional: defaultParams for this inline step
|
||||
defaultParams?: Record<string, any> | ((ctx: TFlowContext) => Record<string, any> | Promise<Record<string, any>>); // Optional: defaultParams for this inline step
|
||||
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
|
||||
// Cannot use a registered action
|
||||
use?: undefined;
|
||||
}
|
||||
|
||||
export type StepDefinition<TModel extends FlowModel = FlowModel> =
|
||||
| ActionStepDefinition<TModel>
|
||||
| InlineStepDefinition<TModel>;
|
||||
export type StepDefinition<TFlowContext extends FlowContext = FlowContext> =
|
||||
| ActionStepDefinition<TFlowContext>
|
||||
| InlineStepDefinition<TFlowContext>;
|
||||
|
||||
/**
|
||||
* Extra context for flow execution - represents the data that will appear in ctx.extra
|
||||
@ -181,7 +182,7 @@ export type FlowExtraContext = Record<string, any>;
|
||||
/**
|
||||
* 参数解析上下文类型,用于 settings 等场景
|
||||
*/
|
||||
export interface ParamsContext<TModel extends FlowModel = FlowModel> {
|
||||
export interface ParamsContext<TModel extends FlowModel<FlowContext> = FlowModel<FlowContext>> {
|
||||
model: TModel;
|
||||
globals: Record<string, any>;
|
||||
app: any;
|
||||
@ -190,7 +191,7 @@ export interface ParamsContext<TModel extends FlowModel = FlowModel> {
|
||||
/**
|
||||
* Action options for registering actions with generic model type support
|
||||
*/
|
||||
export interface ActionOptions<TModel extends FlowModel = FlowModel, P = any, R = any> {
|
||||
export interface ActionOptions<TModel extends FlowModel<FlowContext> = FlowModel<FlowContext>, P = any, R = any> {
|
||||
handler: (ctx: FlowContext<TModel>, params: P) => Promise<R> | R;
|
||||
uiSchema?: Record<string, any>;
|
||||
defaultParams?: Partial<P> | ((ctx: ParamsContext<TModel>) => Partial<P> | Promise<Partial<P>>);
|
||||
@ -247,7 +248,7 @@ export interface CreateModelOptions {
|
||||
sortIndex?: number; // 排序索引
|
||||
[key: string]: any; // 允许额外的自定义选项
|
||||
}
|
||||
export interface IFlowModelRepository<T extends FlowModel = FlowModel> {
|
||||
export interface IFlowModelRepository<T extends FlowModel<FlowContext> = FlowModel<FlowContext>> {
|
||||
findOne(query: Record<string, any>): Promise<Record<string, any> | null>;
|
||||
save(model: T): Promise<Record<string, any>>;
|
||||
destroy(uid: string): Promise<boolean>;
|
||||
@ -273,11 +274,11 @@ export interface RequiredConfigStepFormDialogProps {
|
||||
dialogTitle?: string;
|
||||
}
|
||||
|
||||
export type SubModelValue<TModel extends FlowModel = FlowModel> = TModel | TModel[];
|
||||
export type SubModelValue<TModel extends FlowModel<FlowContext> = FlowModel<FlowContext>> = TModel | TModel[];
|
||||
|
||||
export interface DefaultStructure {
|
||||
parent?: any;
|
||||
subModels?: Record<string, FlowModel | FlowModel[]>;
|
||||
subModels?: Record<string, FlowModel<FlowContext> | FlowModel<FlowContext>[]>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -119,6 +119,6 @@ export class FlowExitException extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export function defineAction(options: ActionDefinition) {
|
||||
export function defineAction<TFlowContext extends FlowContext>(options: ActionDefinition<TFlowContext>) {
|
||||
return options;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user