Act-Master

website·Indexed 30 days ago

https://avil13.github.io/act-master/

A framework for separating business logic from the view, featuring ActMasterAction for logical units, a dedicated CLI, and integrations for Vue 3, React, and VanillaJS. Includes utilities for execution, subscription management, and testing via ActMaster test-utils.

Tokens
14.3K
Snippets
90
Records
101
Agent score
74%

What's inside Act-Master

  1. Overview of act-master-cli

    The act-master-cli is a utility included as a dependency of act-master (no separate installation required). It simplifies the management of actions spread across multiple folders by collecting named exported action classes and fn2act / functionToAction calls into a single file. This enables strict typing, type substitution, and type correctness checks.

    Discovery rules:

    • It collects named exported action classes and exported fn2act/functionToAction calls.
    • It is recommended to keep one action per file.
    • If a file contains an action class, class discovery takes precedence.
    • Plain object actions must be registered manually.
  2. Bootstrap act-master using Class Style

    Instantiate the ActMaster class directly to manage actions in a class-based architecture.
    import { ActMaster, type ActMasterOptions } from 'act-master';
    import { actions } from '../act/actions';
    
    const options: ActMasterOptions = {
      actions,
      errorHandlerEventName: 'OnError',
    };
    
    const $act = new ActMaster(options);
  3. Subscribe to action results

    You can listen for the successful execution of an action by subscribing to it. The subscribe and on methods are synonymous. When the action executes successfully, the result is passed to the provided callback.
    import { act } from 'act-master';
    
    // Using subscribe
    act().subscribe('Login', (loginData) => {
      console.log('Login successful:', loginData);
    });
    
    // Using on (synonymous)
    act().on('Login', (loginData) => {
      console.log('Login successful:', loginData);
    });
  4. Manage multiple subscriptions using subsList

    v1

    To avoid memory leaks and the need to store multiple unsubscribe variables, use this.$act.subsList. This utility allows you to group subscriptions under a specific key (any object that can be stored in a Map) and clear them all at once.

    • this.$act.subsList.add(key, ...unsubscribes): Associates one or more unsubscribe functions with a key.
    • this.$act.subsList.clear(key): Executes all unsubscribe functions associated with the provided key and removes them from the list.
    export default {
      mounted() {
        const unsubscribe1 = this.$act.subscribe('GetDta', (data) => { /* ... */ });
        const unsubscribe2 = this.$act.inProgress('GetDta', (isLoading) => {
          this.isLoading = isLoading;
        });
    
        // Store subscriptions using a string key
        this.$act.subsList.add('CACHE_KEY', unsubscribe1, unsubscribe2);
      },
    
      beforeDestroy() {
        // Clear all subscriptions associated with 'CACHE_KEY'
        this.$act.subsList.clear('CACHE_KEY');
      }
    }
  5. Convert a function to an action using fn2act

    v1
    When business logic is simple enough to be contained in a single function, you can use the fn2act handler (or its alias functionToAction) to convert that function into an action object. The resulting action will inherit the name of the original function, allowing it to be registered and executed via the act-master framework.
    import { fn2act, act } from 'act-master';
    
    function SumAction(a, b) {
      return a + b;
    }
    
    act.addActions([
      fn2act(SumAction), // Convert function to action
    ]);
    
    const result = await act.exec('SumAction', 2, 3);
    console.log(result); // 5
  6. Emit multiple results from a single action using emit()

    v1
    In vue-act-master, you can change the value of a result multiple times during a single action's execution by using the emit function. This is useful for clearing previous state (e.g., setting a list to an empty array) immediately before starting an asynchronous request to fetch new data. To enable this, the action class must implement a method (such as useEmit) to receive the EmitAction emitter.
    // with-emit-action.ts
    import { ActMasterAction, EmitAction } from 'vue-act-master';
    import { api } from '../you/api';
    
    export class WithEmitAction implements ActMasterAction {
      name = 'GetItems';
    
      private emit: EmitAction;
    
      exec() {
        // 1. Immediately emit an empty array to clear the UI state
        this.emit([]); 
    
        // 2. Return the promise/value from the API to set the final state
        return api.getItems(); 
      }
    
      // This method is used by the framework to inject the emitter
      useEmit(emit: EmitAction) {
        this.emit = emit;
      }
    }
  7. Emit multiple results from a single action using EmitAction

    v1

    In vue-act-master, you can change the value of an action's result multiple times during its execution by using the EmitAction function. This is useful for clearing previous state (like resetting a list to an empty array) immediately before starting an asynchronous request to fetch new data.

    To implement this, the action class must implement ActMasterAction and provide a method (e.g., useEmit) to receive the EmitAction instance from the framework.

    // with-emit-action.ts
    import { ActMasterAction, EmitAction } from 'vue-act-master';
    import { api } from '../you/api';
    
    export class WithEmitAction implements ActMasterAction {
      name = 'GetItems';
    
      private emit: EmitAction;
    
      exec() {
        // 1. Immediately emit an empty array to clear the UI state
        this.emit([]); 
    
        // 2. Return the promise/value from the API to set the final state
        return api.getItems(); 
      }
    
      // The framework calls this to set the Emitter
      useEmit(emit: EmitAction) {
        this.emit = emit;
      }
    }
  8. Create a basic ActMasterAction

    v1
    An Action in vue-act-master is an object used to store business logic, implementing the Commander pattern. It requires a name property and an exec method. Actions can be defined as simple objects or as classes (recommended for TypeScript).
    import { ActMasterAction } from 'vue-act-master';
    
    // Simple object approach
    export const dataAction: ActMasterAction = {
      name: 'get.data',
      async exec(id = 1) {
        const response = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`);
        return response.json();
      },
    };
    
    // Class-based approach (Recommended)
    export class ClassAction implements ActMasterAction {
      name = 'get.data';
      async exec() {
        const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
        return response.json();
      }
    }
  9. Unsubscribe from action results

    To prevent memory leaks, you must unsubscribe from actions when they are no longer needed. There are three primary ways to unsubscribe:

    1. Return Function: The subscribe/on method returns a function that, when called, unsubscribes the listener.
    2. Explicit Method: Use unsubscribe or off (synonymous) by passing the action name and the original callback.
    3. Once: Use the once method to subscribe to a single execution result and automatically unsubscribe immediately after.
    import { act } from 'act-master';
    
    const loginCallback = (data) => console.log(data);
    
    // Method 1: Using the returned function
    const unsubscribe = act().subscribe('Login', loginCallback);
    unsubscribe();
    
    // Method 2: Using explicit unsubscribe/off
    act().subscribe('Login', loginCallback);
    act().unsubscribe('Login', loginCallback);
    
    // Method 3: Single-use subscription
    act().once('Login', loginCallback);
  10. Use ActTest to write tests for ActMaster actions

    v1
    The ActTest class is a singleton utility used to simplify testing for act-master. It provides a way to instantiate the ActMaster core, execute actions, and verify results or subscriptions in a test environment. To begin, use ActTest.getInstance(options?: ActMasterOptions) to get the ActMaster instance. It is recommended to call ActTest.resetAll() in a beforeEach block to ensure test isolation.
    import { ActTest } from 'vue-act-master'; // or 'act-master';
    
    const $act = ActTest.getInstance();
    
    beforeEach(() => {
      ActTest.resetAll();
    });
    
    it('Example result', async () => {
      const action: ActMasterAction = {
        name: 'SomeName',
        exec() {
          return 42;
        }
      };
    
      $act.addActions([action]);
      await $act.exec('SomeName');
    
      expect(ActTest.getLastResult()).toBe(42);
    });
  11. Manage subscriptions in Vue with useAutoUnsubscribe

    The useAutoUnsubscribe helper from act-master/vue automatically removes subscriptions when a Vue component is unmounted (onBeforeUnmount). It must be called synchronously inside setup() or <script setup> before registering subscriptions.

    Alternatively, you can pass a Vue cleanup hook (like onBeforeUnmount) as the third argument to the on method for a single subscription.

    <script setup lang="ts">
    import { ref } from 'vue';
    import { act } from 'act-master';
    import { useAutoUnsubscribe } from 'act-master/vue';
    import { onBeforeUnmount } from 'vue';
    
    const balance = ref<number | null>(null);
    
    // Automatically cleans up all subsequent subscriptions in this component
    useAutoUnsubscribe();
    
    act().on('GetBalance', (value) => {
      balance.value = value;
    });
    
    // Alternative: Single subscription with explicit cleanup hook
    act().on('OtherAction', (data) => {
      console.log(data);
    }, onBeforeUnmount);
    </script>
    
    <template>
      <button @click="act().exec('GetBalance', '2026.01.01')">Load balance</button>
      <p>Balance: {{ balance ?? 'Not loaded' }}</p>
    </template>
  12. Validate action input arguments

    v1
    Implement the validateInput method in your action class to validate arguments before exec is called. Return true if valid, or return a CancelledAct instance to stop execution and provide an error message.
    import { ActMasterAction, CancelledAct } from 'vue-act-master';
    
    export class DataAction implements ActMasterAction {
      name = 'get.data';
    
      validateInput(arg?: any): true | CancelledAct {
        if (typeof arg !== 'number') {
          return new CancelledAct('Validation error', { id: 'Must be a number' });
        }
        return true;
      }
    
      async exec(id: number) {
        // ... logic
      }
    }
    
    // Handling the result in a component
    const result = await this.$act.exec('get.data', '101');
    if (result instanceof CancelledAct) {
      // Handle validation error
    }