diff --git a/packages/core/client/docs/zh-CN/core/flow-actions/demos/confirm.tsx b/packages/core/client/docs/zh-CN/core/flow-actions/demos/confirm.tsx index 1a954e1228..64605ab8af 100644 --- a/packages/core/client/docs/zh-CN/core/flow-actions/demos/confirm.tsx +++ b/packages/core/client/docs/zh-CN/core/flow-actions/demos/confirm.tsx @@ -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'; diff --git a/packages/core/flow-engine/src/models/flowModel.tsx b/packages/core/flow-engine/src/models/flowModel.tsx index b0aa0a1edf..cdd31e2c26 100644 --- a/packages/core/flow-engine/src/models/flowModel.tsx +++ b/packages/core/flow-engine/src/models/flowModel.tsx @@ -38,7 +38,10 @@ const modelMetas = new WeakMap(); // 使用WeakMap存储每个类的flows const modelFlows = new WeakMap>(); -export class FlowModel { +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} [flowDefinition] 当第一个参数为流程 Key 时,此参数为流程的定义。 * @returns {void} */ - public static registerFlow FlowModel>( - this: TModel, - keyOrDefinition: string | FlowDefinition>, - flowDefinition?: Omit>, 'key'> & { key?: string }, + public static registerFlow< + TFlowContext extends FlowContext = FlowContext, + TStructure extends { parent?: any; subModels?: any } = DefaultStructure, + >( + this: FlowModel, + keyOrDefinition: string | FlowDefinition, + flowDefinition?: Omit, 'key'> & { key?: string }, ): void { - let definition: FlowDefinition>; + let definition: FlowDefinition; let key: string; if (typeof keyOrDefinition === 'string' && flowDefinition) { @@ -184,7 +190,11 @@ export class FlowModel} [extendDefinition] 当第一个参数为流程 Key 时,此参数为流程的扩展定义。 * @returns {void} */ - public static extendFlow( + public static extendFlow< + TFlowContext extends FlowContext = FlowContext, + TStructure extends { parent?: any; subModels?: any } = DefaultStructure, + >( + this: FlowModel, keyOrDefinition: string | ExtendedFlowDefinition, extendDefinition?: Omit, ): void { @@ -214,7 +224,7 @@ export class FlowModel); + this.registerFlow(newFlowDef as FlowDefinition); return; } @@ -222,7 +232,7 @@ export class FlowModel); + this.registerFlow(mergedFlow as FlowDefinition); } /** @@ -367,7 +377,7 @@ export class FlowModel = { + const flowContext: TFlowContext = { exit: () => { throw new FlowExitException(flowKey, this.uid); }, @@ -383,12 +393,12 @@ export class FlowModel, params: any) => Promise | any) | undefined; + let handler: ((ctx: TFlowContext, params: any) => Promise | any) | undefined; let combinedParams: Record = {}; let actionDefinition; @@ -478,7 +488,7 @@ export class FlowModel(this: T, flows: ExtendedFlowDefinition[] = []): T { - class CustomFlowModel extends (this as unknown as typeof FlowModel) { + class CustomFlowModel extends (this as unknown as typeof FlowModel) { // @ts-ignore static name = `CustomFlowModel_${generateUid()}`; } @@ -552,7 +562,7 @@ export class FlowModel; } - setParent(parent: FlowModel): void { + setParent(parent: FlowModel): void { if (!parent || !(parent instanceof FlowModel)) { throw new Error('Parent must be an instance of FlowModel.'); } @@ -560,8 +570,8 @@ export class FlowModel) { + let model: FlowModel; 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) { + let model: FlowModel; 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 ({ + data.subModels[subModelKey] = this.subModels[subModelKey].map((model: FlowModel, index) => ({ ...model.serialize(), sortIndex: index, })); @@ -783,6 +793,8 @@ export class FlowModel(definition: FlowDefinition): FlowDefinition { +export function defineFlow = FlowModel>( + definition: FlowDefinition, +): FlowDefinition { return definition as FlowDefinition; } diff --git a/packages/core/flow-engine/src/models/typeDemo.tsx b/packages/core/flow-engine/src/models/typeDemo.tsx new file mode 100644 index 0000000000..5c20950e84 --- /dev/null +++ b/packages/core/flow-engine/src/models/typeDemo.tsx @@ -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> { + injectedValue: { + abc: string; + }; + + render() { + return null; + } +} + +const demoAction = defineAction>({ + name: 'demoAction', + title: 'Demo Action', + uiSchema: {}, + handler(ctx, params) {}, +}); + +TypeDemoModel.registerFlow({ + key: 'typeDemo', + title: 'Type Demo', + on: { + eventName: 'click', + }, + steps: { + step1: { + handler(ctx) {}, + }, + }, +}); diff --git a/packages/core/flow-engine/src/types.ts b/packages/core/flow-engine/src/types.ts index f3e0929981..061404818d 100644 --- a/packages/core/flow-engine/src/types.ts +++ b/packages/core/flow-engine/src/types.ts @@ -38,7 +38,7 @@ export type DeepPartial = { /** * Defines a flow with generic model type support. */ -export interface FlowDefinition { +export interface FlowDefinition { key: string; // Unique identifier for the flow title?: string; /** @@ -56,7 +56,7 @@ export interface FlowDefinition { on?: { eventName: string; }; - steps: Record>; + steps: Record>; } // 扩展FlowDefinition类型,添加partial标记用于部分覆盖 @@ -87,7 +87,12 @@ export type ReadonlyModelProps = Readonly; /** * Context object passed to handlers during flow execution. */ -export interface FlowContext { +export interface FlowContext< + TModel extends FlowModel = FlowModel, + TExtra extends FlowExtraContext = FlowExtraContext, + TShared extends Record = Record, + TGlobals extends Record = Record, +> { exit: () => void; // Terminate the entire flow execution logger: { info: (message: string, meta?: any) => void; @@ -96,19 +101,19 @@ export interface FlowContext { debug: (message: string, meta?: any) => void; }; stepResults: Record; // Results from previous steps - shared: Record; // Shared data within the flow (read/write) - globals: Record; // Global context data (read-only) - extra: Record; // 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; /** * Constructor for model classes. */ -export type ModelConstructor = new (options: { +export type ModelConstructor = FlowModel> = new (options: { uid: string; props?: IModelComponentProps; stepParams?: StepParams; @@ -120,20 +125,18 @@ export type ModelConstructor = new (options: { /** * Defines a reusable action with generic model type support. */ -export interface ActionDefinition { +export interface ActionDefinition { name: string; // Unique identifier for the action title?: string; - handler: (ctx: FlowContext, params: any) => Promise | any; + handler: (ctx: TFlowContext, params: any) => Promise | any; uiSchema?: Record; - defaultParams?: - | Record - | ((ctx: ParamsContext) => Record | Promise>); + defaultParams?: Record | ((ctx: TFlowContext) => Record | Promise>); } /** * Base interface for a step definition with generic model type support. */ -interface BaseStepDefinition { +interface BaseStepDefinition { title?: string; isAwait?: boolean; // Whether to await the handler, defaults to true } @@ -141,12 +144,11 @@ interface BaseStepDefinition { /** * Step that uses a registered Action with generic model type support. */ -export interface ActionStepDefinition extends BaseStepDefinition { +export interface ActionStepDefinition + extends BaseStepDefinition { use: string; // Name of the registered ActionDefinition uiSchema?: Record; // Optional: overrides uiSchema from ActionDefinition - defaultParams?: - | Record - | ((ctx: ParamsContext) => Record | Promise>); // Optional: overrides/extends defaultParams from ActionDefinition + defaultParams?: Record | ((ctx: TFlowContext) => Record | Promise>); // 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 exte /** * Step that defines its handler inline with generic model type support. */ -export interface InlineStepDefinition extends BaseStepDefinition { - handler: (ctx: FlowContext, params: any) => Promise | any; +export interface InlineStepDefinition + extends BaseStepDefinition { + handler: (ctx: TFlowContext, params: any) => Promise | any; uiSchema?: Record; // Optional: uiSchema for this inline step - defaultParams?: - | Record - | ((ctx: ParamsContext) => Record | Promise>); // Optional: defaultParams for this inline step + defaultParams?: Record | ((ctx: TFlowContext) => Record | Promise>); // 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 = - | ActionStepDefinition - | InlineStepDefinition; +export type StepDefinition = + | ActionStepDefinition + | InlineStepDefinition; /** * Extra context for flow execution - represents the data that will appear in ctx.extra @@ -181,7 +182,7 @@ export type FlowExtraContext = Record; /** * 参数解析上下文类型,用于 settings 等场景 */ -export interface ParamsContext { +export interface ParamsContext = FlowModel> { model: TModel; globals: Record; app: any; @@ -190,7 +191,7 @@ export interface ParamsContext { /** * Action options for registering actions with generic model type support */ -export interface ActionOptions { +export interface ActionOptions = FlowModel, P = any, R = any> { handler: (ctx: FlowContext, params: P) => Promise | R; uiSchema?: Record; defaultParams?: Partial

| ((ctx: ParamsContext) => Partial

| Promise>); @@ -247,7 +248,7 @@ export interface CreateModelOptions { sortIndex?: number; // 排序索引 [key: string]: any; // 允许额外的自定义选项 } -export interface IFlowModelRepository { +export interface IFlowModelRepository = FlowModel> { findOne(query: Record): Promise | null>; save(model: T): Promise>; destroy(uid: string): Promise; @@ -273,11 +274,11 @@ export interface RequiredConfigStepFormDialogProps { dialogTitle?: string; } -export type SubModelValue = TModel | TModel[]; +export type SubModelValue = FlowModel> = TModel | TModel[]; export interface DefaultStructure { parent?: any; - subModels?: Record; + subModels?: Record | FlowModel[]>; } /** diff --git a/packages/core/flow-engine/src/utils.ts b/packages/core/flow-engine/src/utils.ts index 682eef536a..1663444b3b 100644 --- a/packages/core/flow-engine/src/utils.ts +++ b/packages/core/flow-engine/src/utils.ts @@ -119,6 +119,6 @@ export class FlowExitException extends Error { } } -export function defineAction(options: ActionDefinition) { +export function defineAction(options: ActionDefinition) { return options; }