ActionDefinition

ActionDefinition mendefinisikan action yang dapat digunakan ulang. Action ini dapat direferensikan di banyak Flow dan Step. Action adalah unit eksekusi inti dalam Flow Engine, yang merangkum logika bisnis spesifik.

Definisi Tipe

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;
}

Cara Pendaftaran

// Pendaftaran global (melalui FlowEngine)
const engine = new FlowEngine();
engine.registerAction({
  name: 'loadDataAction',
  title: 'Load Data',
  handler: async (ctx, params) => {
    // Logika pemrosesan
  }
});

// Pendaftaran level model (melalui FlowModel)
class MyModel extends FlowModel {}
MyModel.registerAction({
  name: 'processDataAction',
  title: 'Process Data',
  handler: async (ctx, params) => {
    // Logika pemrosesan
  }
});

// Penggunaan dalam Flow
MyModel.registerFlow({
  key: 'dataFlow',
  steps: {
    step1: {
      use: 'loadDataAction',  // Mereferensikan action global
    },
    step2: {
      use: 'processDataAction', // Mereferensikan action level model
    }
  }
});

Penjelasan Properti

name

Tipe: string
Wajib: Ya
Deskripsi: Identifier unik untuk action

Digunakan untuk mereferensikan action dalam step melalui properti use.

Contoh:

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

title

Tipe: string
Wajib: Tidak
Deskripsi: Judul tampilan action

Digunakan untuk tampilan UI dan debugging.

Contoh:

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

handler

Tipe: (ctx: TCtx, params: any) => Promise<any> | any
Wajib: Ya
Deskripsi: Fungsi handler action

Logika inti dari action, menerima context dan parameter, mengembalikan hasil pemrosesan.

Contoh:

handler: async (ctx, params) => {
  const { model, flowEngine } = ctx;
  
  try {
    // Eksekusi logika spesifik
    const result = await performAction(params);
    
    // Mengembalikan hasil
    return {
      success: true,
      data: result,
      message: 'Action completed successfully'
    };
  } catch (error) {
    return {
      success: false,
      error: error.message
    };
  }
}

defaultParams

Tipe: Record<string, any> | ((ctx: TCtx) => Record<string, any> | Promise<Record<string, any>>)
Wajib: Tidak
Deskripsi: Parameter default action

Sebelum action dieksekusi, mengisi nilai default untuk parameter.

Contoh:

// Parameter default statis
defaultParams: {
  timeout: 5000,
  retries: 3,
  format: 'json'
}

// Parameter default dinamis
defaultParams: (ctx) => {
  return {
    userId: ctx.model.uid,
    timestamp: Date.now(),
    version: ctx.flowEngine.version
  }
}

// Parameter default asynchronous
defaultParams: async (ctx) => {
  const config = await loadConfiguration();
  return {
    apiUrl: config.apiUrl,
    apiKey: config.apiKey,
    timeout: config.timeout
  }
}

uiSchema

Tipe: Record<string, ISchema> | ((ctx: TCtx) => Record<string, ISchema> | Promise<Record<string, ISchema>>)
Wajib: Tidak
Deskripsi: Mode konfigurasi UI action

Mendefinisikan cara tampilan action di UI dan konfigurasi form.

Contoh:

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

Tipe: (ctx: FlowSettingsContext<TModel>, params: any, previousParams: any) => void | Promise<void>
Wajib: Tidak
Deskripsi: Hook function sebelum parameter disimpan

Dieksekusi sebelum parameter action disimpan, dapat digunakan untuk validasi atau konversi parameter.

Contoh:

beforeParamsSave: (ctx, params, previousParams) => {
  // Validasi parameter
  if (!params.url) {
    throw new Error('URL is required');
  }
  
  // Konversi parameter
  params.url = params.url.trim();
  if (!params.url.startsWith('http')) {
    params.url = 'https://' + params.url;
  }
  
  // Mencatat perubahan
  console.log('Parameters changed:', {
    from: previousParams,
    to: params
  });
}

afterParamsSave

Tipe: (ctx: FlowSettingsContext<TModel>, params: any, previousParams: any) => void | Promise<void>
Wajib: Tidak
Deskripsi: Hook function setelah parameter disimpan

Dieksekusi setelah parameter action disimpan, dapat digunakan untuk memicu operasi lainnya.

Contoh:

afterParamsSave: (ctx, params, previousParams) => {
  // Mencatat log
  console.log('Action params saved:', params);
  
  // Memicu event
  ctx.model.emitter.emit('actionParamsChanged', {
    actionName: 'loadDataAction',
    params,
    previousParams
  });
  
  // Memperbarui cache
  ctx.model.updateCache('actionParams', params);
}

useRawParams

Tipe: boolean | ((ctx: TCtx) => boolean | Promise<boolean>)
Wajib: Tidak
Deskripsi: Apakah menggunakan parameter mentah

Jika true, parameter mentah akan langsung diteruskan ke fungsi handler tanpa pemrosesan apa pun.

Contoh:

// Konfigurasi statis
useRawParams: true

// Konfigurasi dinamis
useRawParams: (ctx) => {
  return ctx.model.isDebugMode;
}

uiMode

Tipe: StepUIMode | ((ctx: FlowRuntimeContext<TModel>) => StepUIMode | Promise<StepUIMode>)
Wajib: Tidak
Deskripsi: Mode tampilan UI action

Mengontrol cara tampilan action di UI.

Mode yang didukung:

  • 'dialog' - Mode dialog
  • 'drawer' - Mode drawer
  • 'embed' - Mode embed
  • Atau objek konfigurasi kustom

Contoh:

// Mode sederhana
uiMode: 'dialog'

// Konfigurasi kustom
uiMode: {
  type: 'dialog',
  props: {
    width: 800,
    title: 'Action Configuration',
    maskClosable: false
  }
}

// Mode dinamis
uiMode: (ctx) => {
  return ctx.model.isMobile ? 'drawer' : 'dialog';
}

scene

Tipe: ActionScene | ActionScene[]
Wajib: Tidak
Deskripsi: Skenario penggunaan action

Membatasi action untuk digunakan dalam skenario tertentu saja.

Skenario yang didukung:

  • 'settings' - Skenario pengaturan
  • 'runtime' - Skenario runtime
  • 'design' - Skenario design

Contoh:

scene: 'settings'  // Hanya digunakan dalam skenario pengaturan
scene: ['settings', 'runtime']  // Digunakan dalam skenario pengaturan dan runtime

sort

Tipe: number
Wajib: Tidak
Deskripsi: Bobot sorting action

Digunakan untuk mengontrol urutan tampilan action di list. Semakin kecil nilai, semakin awal posisinya.

Contoh:

sort: 0  // Paling awal
sort: 10 // Posisi tengah
sort: 100 // Lebih akhir

Contoh Lengkap

class DataProcessingModel extends FlowModel {}

// Mendaftarkan action loading data
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
});

// Mendaftarkan action pemrosesan data
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
});