ActionDefinition

ActionDefinition định nghĩa các Action có thể tái sử dụng, các Action này có thể được tham chiếu trong nhiều Flow và Step. Action là đơn vị thực thi cốt lõi trong Flow engine, đóng gói logic nghiệp vụ cụ thể.

Định nghĩa kiểu

interface ActionDefinition<TModel extends FlowModel = FlowModel, TCtx extends FlowContext = FlowContext> {
  name: string;
  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;
}

Cách đăng ký

// Đăng ký toàn cục (qua FlowEngine)
const engine = new FlowEngine();
engine.registerAction({
  name: 'loadDataAction',
  title: 'Load Data',
  handler: async (ctx, params) => {
    // Logic xử lý
  }
});

// Đăng ký cấp Model (qua FlowModel)
class MyModel extends FlowModel {}
MyModel.registerAction({
  name: 'processDataAction',
  title: 'Process Data',
  handler: async (ctx, params) => {
    // Logic xử lý
  }
});

// Sử dụng trong Flow
MyModel.registerFlow({
  key: 'dataFlow',
  steps: {
    step1: {
      use: 'loadDataAction',  // Tham chiếu Action toàn cục
    },
    step2: {
      use: 'processDataAction', // Tham chiếu Action cấp Model
    }
  }
});

Mô tả thuộc tính

name

Kiểu: string
Bắt buộc: Có
Mô tả: Định danh duy nhất của Action

Dùng để tham chiếu Action trong Step thông qua thuộc tính use.

Ví dụ:

name: 'loadDataAction'
name: 'processDataAction'
name: 'saveDataAction'

title

Kiểu: string
Bắt buộc: Không
Mô tả: Tiêu đề hiển thị của Action

Dùng để hiển thị trên giao diện và debug.

Ví dụ:

title: 'Load Data'
title: 'Process Information'
title: 'Save Results'

handler

Kiểu: (ctx: TCtx, params: any) => Promise<any> | any
Bắt buộc: Có
Mô tả: Hàm xử lý của Action

Logic cốt lõi của Action, nhận ctx và params, trả về kết quả xử lý.

Ví dụ:

handler: async (ctx, params) => {
  const { model, flowEngine } = ctx;
  
  try {
    // Thực thi logic cụ thể
    const result = await performAction(params);
    
    // Trả về kết quả
    return {
      success: true,
      data: result,
      message: 'Action completed successfully'
    };
  } catch (error) {
    return {
      success: false,
      error: error.message
    };
  }
}

defaultParams

Kiểu: Record<string, any> | ((ctx: TCtx) => Record<string, any> | Promise<Record<string, any>>)
Bắt buộc: Không
Mô tả: Tham số mặc định của Action

Trước khi Action thực thi, điền giá trị mặc định cho tham số.

Ví dụ:

// Tham số mặc định tĩnh
defaultParams: {
  timeout: 5000,
  retries: 3,
  format: 'json'
}

// Tham số mặc định động
defaultParams: (ctx) => {
  return {
    userId: ctx.model.uid,
    timestamp: Date.now(),
    version: ctx.flowEngine.version
  }
}

// Tham số mặc định bất đồng bộ
defaultParams: async (ctx) => {
  const config = await loadConfiguration();
  return {
    apiUrl: config.apiUrl,
    apiKey: config.apiKey,
    timeout: config.timeout
  }
}

uiSchema

Kiểu: Record<string, ISchema> | ((ctx: TCtx) => Record<string, ISchema> | Promise<Record<string, ISchema>>)
Bắt buộc: Không
Mô tả: Schema cấu hình UI của Action

Định nghĩa cách Action hiển thị trên giao diện và cấu hình form.

Ví dụ:

uiSchema: {
  'x-component': 'Form',
  'x-component-props': {
    layout: 'vertical',
    labelCol: { span: 6 },
    wrapperCol: { span: 18 }
  },
  properties: {
    url: {
      type: 'string',
      title: 'API URL',
      'x-component': 'Input',
      'x-decorator': 'FormItem',
      required: true
    },
    method: {
      type: 'string',
      title: 'HTTP Method',
      'x-component': 'Select',
      'x-decorator': 'FormItem',
      enum: ['GET', 'POST', 'PUT', 'DELETE'],
      default: 'GET'
    },
    timeout: {
      type: 'number',
      title: 'Timeout (ms)',
      'x-component': 'InputNumber',
      'x-decorator': 'FormItem',
      default: 5000
    }
  }
}

beforeParamsSave

Kiểu: (ctx: FlowSettingsContext<TModel>, params: any, previousParams: any) => void | Promise<void>
Bắt buộc: Không
Mô tả: Hook trước khi lưu tham số

Thực thi trước khi lưu tham số Action, có thể dùng để xác thực hoặc chuyển đổi tham số.

Ví dụ:

beforeParamsSave: (ctx, params, previousParams) => {
  // Xác thực tham số
  if (!params.url) {
    throw new Error('URL is required');
  }
  
  // Chuyển đổi tham số
  params.url = params.url.trim();
  if (!params.url.startsWith('http')) {
    params.url = 'https://' + params.url;
  }
  
  // Ghi nhận thay đổi
  console.log('Parameters changed:', {
    from: previousParams,
    to: params
  });
}

afterParamsSave

Kiểu: (ctx: FlowSettingsContext<TModel>, params: any, previousParams: any) => void | Promise<void>
Bắt buộc: Không
Mô tả: Hook sau khi lưu tham số

Thực thi sau khi lưu tham số Action, có thể dùng để kích hoạt các thao tác khác.

Ví dụ:

afterParamsSave: (ctx, params, previousParams) => {
  // Ghi log
  console.log('Action params saved:', params);
  
  // Kích hoạt event
  ctx.model.emitter.emit('actionParamsChanged', {
    actionName: 'loadDataAction',
    params,
    previousParams
  });
  
  // Cập nhật cache
  ctx.model.updateCache('actionParams', params);
}

useRawParams

Kiểu: boolean | ((ctx: TCtx) => boolean | Promise<boolean>)
Bắt buộc: Không
Mô tả: Có sử dụng tham số gốc hay không

Nếu là true, sẽ truyền tham số gốc trực tiếp cho hàm xử lý mà không qua bất kỳ xử lý nào.

Ví dụ:

// Cấu hình tĩnh
useRawParams: true

// Cấu hình động
useRawParams: (ctx) => {
  return ctx.model.isDebugMode;
}

uiMode

Kiểu: StepUIMode | ((ctx: FlowRuntimeContext<TModel>) => StepUIMode | Promise<StepUIMode>)
Bắt buộc: Không
Mô tả: Chế độ hiển thị UI của Action

Điều khiển cách hiển thị Action trên giao diện.

Các chế độ hỗ trợ:

  • 'dialog' - Chế độ hộp thoại
  • 'drawer' - Chế độ drawer
  • 'embed' - Chế độ nhúng
  • Hoặc đối tượng cấu hình tùy chỉnh

Ví dụ:

// Chế độ đơn giản
uiMode: 'dialog'

// Cấu hình tùy chỉnh
uiMode: {
  type: 'dialog',
  props: {
    width: 800,
    title: 'Action Configuration',
    maskClosable: false
  }
}

// Chế độ động
uiMode: (ctx) => {
  return ctx.model.isMobile ? 'drawer' : 'dialog';
}

scene

Kiểu: ActionScene | ActionScene[]
Bắt buộc: Không
Mô tả: Kịch bản sử dụng của Action

Giới hạn Action chỉ được sử dụng trong các kịch bản nhất định.

Các kịch bản hỗ trợ:

  • 'settings' - Kịch bản cấu hình
  • 'runtime' - Kịch bản runtime
  • 'design' - Kịch bản thiết kế

Ví dụ:

scene: 'settings'  // Chỉ dùng trong kịch bản cấu hình
scene: ['settings', 'runtime']  // Dùng trong kịch bản cấu hình và runtime

sort

Kiểu: number
Bắt buộc: Không
Mô tả: Trọng số sắp xếp của Action

Dùng để điều khiển thứ tự hiển thị của Action trong danh sách, giá trị càng nhỏ càng đứng trước.

Ví dụ:

sort: 0  // Đứng đầu
sort: 10 // Vị trí giữa
sort: 100 // Đứng sau

Ví dụ đầy đủ

class DataProcessingModel extends FlowModel {}

// Đăng ký Action tải dữ liệu
DataProcessingModel.registerAction({
  name: 'loadDataAction',
  title: 'Load Data',
  handler: async (ctx, params) => {
    const { url, method = 'GET', timeout = 5000 } = params;
    
    try {
      const response = await fetch(url, {
        method,
        timeout,
        headers: {
          'Content-Type': 'application/json'
        }
      });
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const data = await response.json();
      
      return {
        success: true,
        data,
        message: 'Data loaded successfully'
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  },
  defaultParams: {
    method: 'GET',
    timeout: 5000
  },
  uiSchema: {
    'x-component': 'Form',
    properties: {
      url: {
        type: 'string',
        title: 'API URL',
        'x-component': 'Input',
        'x-decorator': 'FormItem',
        required: true
      },
      method: {
        type: 'string',
        title: 'HTTP Method',
        'x-component': 'Select',
        'x-decorator': 'FormItem',
        enum: ['GET', 'POST', 'PUT', 'DELETE']
      },
      timeout: {
        type: 'number',
        title: 'Timeout (ms)',
        'x-component': 'InputNumber',
        'x-decorator': 'FormItem'
      }
    }
  },
  beforeParamsSave: (ctx, params) => {
    if (!params.url) {
      throw new Error('URL is required');
    }
    params.url = params.url.trim();
  },
  afterParamsSave: (ctx, params) => {
    ctx.model.emitter.emit('dataSourceChanged', params);
  },
  uiMode: 'dialog',
  scene: ['settings', 'runtime'],
  sort: 0
});

// Đăng ký Action xử lý dữ liệu
DataProcessingModel.registerAction({
  name: 'processDataAction',
  title: 'Process Data',
  handler: async (ctx, params) => {
    const { data, processor, options = {} } = params;
    
    try {
      const processedData = await processData(data, processor, options);
      
      return {
        success: true,
        data: processedData,
        message: 'Data processed successfully'
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  },
  defaultParams: (ctx) => ({
    processor: 'default',
    options: {
      format: 'json',
      encoding: 'utf8'
    }
  }),
  uiSchema: {
    'x-component': 'Form',
    properties: {
      processor: {
        type: 'string',
        title: 'Processor',
        'x-component': 'Select',
        'x-decorator': 'FormItem',
        enum: ['default', 'advanced', 'custom']
      },
      options: {
        type: 'object',
        title: 'Options',
        'x-component': 'Form',
        'x-decorator': 'FormItem',
        properties: {
          format: {
            type: 'string',
            title: 'Format',
            'x-component': 'Select',
            enum: ['json', 'xml', 'csv']
          },
          encoding: {
            type: 'string',
            title: 'Encoding',
            'x-component': 'Select',
            enum: ['utf8', 'ascii', 'latin1']
          }
        }
      }
    }
  },
  scene: 'runtime',
  sort: 1
});