Applications

StateWalker Orchestrator enables the creation and execution of applications composed from one or multiple fragments. Each fragment represents a plain JavaScript module that exports fields recognized by the framework.

An application fragment serves as a building block for constructing modular applications. The framework automatically recognizes exported fields and uses them to compose the final application. This modular approach allows developers to create, develop, and load different fragments independently.

A simple light bulb application demonstrates the fragment structure:

// Unique name of the process
export const name = "app.lightBulb";

// Optional state machine configuration.
// If no layers contain a process configuration then it is
// considered that the process contains just one state - "Main"
export const config = {
  key: "LightBulb",
  transitions: [
    ["", "*", "Off"], // Initial transition to Off state
    ["Off", "toggle", "On"],
    ["On", "toggle", "Off"]
  ]
};

// Context initialization

type ApplicationContext = Record<string, unknown>
export function init(parentContext: Record<string, unknown>): ApplicationContext {
  return { switchCount: 0, parent: parentContext };
}

// State handlers

function LightBulb(context: ApplicationContext) {
  console.log("Light bulb initialized");
}

// An event generator
async function* LightBulbTrigger(context: ApplicationContext)
: AsyncGenerator<string> {
  while (true) {
    await new Promise(resolve => setTimeout(resolve, 1000));
  }
}

function On(context: ApplicationContext) {
  context.switchCount++;
  console.log("Light is on");
}

function Off(context: ApplicationContext) {
  console.log("Light is off");
}


// Export handlers as default object
export default {
  LightBulb,
  LightBulbTrigger,
  On,
  Off
};

The framework supports layered applications through two approaches. The first approach uses a single module that exports multiple handlers for the same state:

export const name = "app.layeredLight";

export default [
  // Business logic layer
  { 
    LightBulb: (context) => {
      context.powerConsumption = 0;
    }
  },
  
  // Control layer
  { 
    LightBulbController: (context) => {
      context.powerConsumption = context.isOn ? 60 : 0;
    }
  },

  // View layer
  { 
    LightBulbView: (context) => {
      updateDisplay(context.isOn, context.powerConsumption);
    }
  }
];

The second approach uses multiple modules bound to the same application name. A core module provides business logic without views:

export const name = "app.modularLight";

export default {
  LightBulb: (context) => {
    initializePowerSystem(context);
  },
  
  LightBulbController: (context) => {
    managePowerConsumption(context);
  },
  
  LightBulbTrigger: async function* () {
    while (true) {
      const sensor = await readMotionSensor();
      if (sensor.motion) yield "motion-detected";
      await sleep(1000);
    }
  }
};

A separate module handles the user interface:

export const name = "app.modularLight";

export default {
  LightBulbView: (context) => {
    renderLightInterface(context);
  }
};

This modular architecture enables dynamic loading of fragments at runtime, allowing applications to extend functionality without modifying core code. Fragments can also override existing functionality by providing new handlers for existing states, with the loading order determining execution precedence.

Fields Definitions

Note: See types definition document for more information about the fragments types.

The name field serves as the sole mandatory property that identifies the application. When multiple fragments share the same name, the framework treats them as providing additional functionality to the same process.

The config field defines the state machine configuration. When multiple fragments provide this field, the framework uses only the last configuration, enabling fragments to override configurations provided by others.

The init method handles application context initialization. This method receives the parent context and returns a new context, allowing customization of context creation. When multiple fragments provide this method, the framework calls them in the order of fragment loading, passing the context returned by the previous fragment to the next one.

State handlers can be provided through either the handlers field or as a default export. When multiple fragments provide handlers for the same state, the framework calls them in the order of fragment loading.