API Reference

API Reference

Complete API documentation for StateFlow functions, types, and patterns.

Core Functions

StateFlow's essential functions for state management:

defineState

Creates a state definition with typed variants using a builder pattern.

function defineState<TProps>(): StateBuilder<TProps, "", unknown, "">

Builder methods:

  • .name(string) - Set unique state name
  • .variant(name, isInitial?) - Add state variant
  • .signals(object) - Define handled signals
  • .parser(func) - Custom property parsing
  • .stringRepr(func) - Custom string representation
  • .build() - Finalize definition

Example:

const taskState = defineState<{
  id: string;
  title: string;
  assignee?: string;
  priority: 'low' | 'medium' | 'high';
}>()
  .name("task")
  .signals(taskSignals)
  .variant("draft", true)
  .variant("assigned")
  .variant("completed")
  .stringRepr(s => `${s.title} (${s.priority})`)
  .parser(obj => ({
    id: obj.id || crypto.randomUUID(),
    title: obj.title || "Untitled",
    assignee: obj.assignee,
    priority: obj.priority || 'medium'
  }))
  .build();

defineSignal

Creates a signal definition for triggering state transitions.

function defineSignal<TArgs>(name: string, stringRepr?: (args: TArgs) => string)

Parameters:

  • name - Unique signal identifier
  • stringRepr - Optional custom string representation

Returns signal factory function.

Example:

// Parameterless signal
const refresh = defineSignal("refresh");
const signal1 = refresh(); // Creates signal instance

// Parameterized signal
const updatePriority = defineSignal<{
  taskId: string;
  priority: 'low' | 'medium' | 'high';
}>("updatePriority");
const signal2 = updatePriority({ 
  taskId: "123", 
  priority: "high" 
});

// Custom string representation
const complexSignal = defineSignal<{
  action: string;
  metadata: Record<string, unknown>;
}>("complexSignal", (args) => `${args.action}:${Object.keys(args.metadata).length} props`);

defineFlow

Defines signal handlers for a state variant.

function defineFlow(state: StateVariant, handlers: SignalHandlers): void

Handler signature: (state, signal, context) => StateResult

Example:

defineFlow(taskState.draft, {
  assign: (state, signal) => {
    if (!signal.userId) {
      return Result.reject("User ID required for assignment");
    }
    return taskState.assigned({
      ...state,
      assignee: signal.userId
    });
  },
  
  updatePriority: (state, signal) => ({
    ...state,
    priority: signal.priority
  }),
  
  delete: () => Result.reject("Cannot delete draft tasks")
});

applyFlow

Applies state definitions to an application object and sets up handlers.

function applyFlow(target: object, states: StateDefinition[], 
                  initializer: (sm: StateManager) => void): void

StateManager methods:

  • addEnterHandler(state, handler) - Called when entering state
  • addExitHandler(state, handler) - Called when leaving state
  • addUpdateHandler(state, handler) - Called when state data changes
  • addRollbackHandler(state, handler) - Called on transition failures

Example:

const app = {
  task: { id: "", title: "", priority: "medium" as const },
  ui: { loading: false, error: null }
};

applyFlow(app, [taskState, uiState], (sm) => {
  sm.addEnterHandler(taskState.assigned, async (state) => {
    return Result.transition(async () => {
      await notifyUser(state.assignee, state);
      return Result.ok();
    }, 3000);
  });
  
  sm.addExitHandler(uiState.error, (state) => {
    clearErrorDisplay();
    return Result.ok();
  });
});

dispatch

Dispatches a signal to trigger state transitions.

function dispatch(target: object, signal: Signal, mute?: boolean): Result

Returns Result indicating the outcome. Use with .expect() and .done().

Example:

// Basic dispatch with expectation
try {
  await dispatch(app, signals.assign({ userId: "user123" }))
    .expect(ResultKind.OK)
    .done();
  console.log("Task assigned successfully");
} catch (error) {
  console.error(`Assignment failed: ${error}`);
}

// Asynchronous dispatch
await dispatch(app, signals.save()).done();
console.log('Save completed');

// Muted dispatch (no logging)
await dispatch(app, signals.ping(), true).done();

// Multiple expected results
await dispatch(app, signals.optionalAction())
  .expect(ResultKind.OK, ResultKind.Ignored)
  .done();

observe

Watches for changes in specific state variants.

function observe(target: object, stateVariants: StateVariant[], 
                handler: (state: StateInstance) => void,
                compareFn?: (prev, curr) => boolean): Disposer

Returns disposable observer. Use [Symbol.dispose]() for cleanup.

Example:

// Basic observation
const observer = observe(
  app,
  [taskState.assigned, taskState.completed],
  (state) => updateTaskDisplay(state)
);

// With custom comparison
const priorityObserver = observe(
  app,
  [taskState.assigned],
  (state) => highlightHighPriority(state),
  (prev, curr) => prev.priority !== curr.priority
);

// Using the using keyword (ES2023)
function watchTasks() {
  using observer = observe(app, [taskState.assigned], updateUI);
  // Observer automatically disposed when scope exits
}

// Manual disposal
const sub = observe(app, [taskState.completed], logCompletion);
// Later...
sub[Symbol.dispose]();

sync

Waits for all pending async transitions to complete.

async function sync(target: object): Promise<void>

Essential before operations requiring stable state.

Example:

async function performComplexOperation() {
  // Ensure no transitions are pending
  await sync(app);
  
  // Now safe to dispatch new signals
  await dispatch(app, signals.startBatch()).done();
  
  // Multiple async operations
  const results = await Promise.all([
    dispatch(app, signals.process({ id: "1" })).done(),
    dispatch(app, signals.process({ id: "2" })).done(),
    dispatch(app, signals.process({ id: "3" })).done()
  ]);
  
  // Wait for all to complete
  await sync(app);
  
  // Continue with next phase
  await dispatch(app, signals.finalizeBatch()).done();
}

Result API

Results provide explicit feedback for all operations with type-safe handling.

Result Types

enum ResultKind {
  OK, Ignored, InTransition, Rejected, Error
}

class Result {
  readonly kind: ResultKind;
  readonly data: unknown;
  readonly error: Error | null;
  readonly message: string | null;
  
  in(...kinds: ResultKind[]): boolean;
  done(): Promise<Result>;
  expect(...kinds: ResultKind[]): this;
}

Static Methods

Result.ok(data?)           // Success
Result.ignore(message)     // Signal not applicable  
Result.reject(message)     // Validation failed
Result.error(error)        // Exception occurred
Result.transition(asyncFn, timeout?) // Async operation

Example:

// In flow handlers
defineFlow(orderState.pending, {
  confirm: (state) => {
    if (!state.paymentVerified) {
      return Result.reject("Payment not verified");
    }
    
    if (state.items.length === 0) {
      return Result.ignore("No items in order");
    }
    
    try {
      validateOrder(state);
      return orderState.confirmed(state);
    } catch (error) {
      return Result.error(error);
    }
  }
});

// In state handlers
sm.addEnterHandler(orderState.processing, (state) => {
  return Result.transition(async () => {
    try {
      await processPayment(state);
      await updateInventory(state);
      return Result.ok();
    } catch (error) {
      return Result.error(error);
    }
  }, 10000); // 10 second timeout
});

Type Utilities

TypeScript utilities for extracting type information:

Infer

Extracts property types from states, variants, or signals.

type UserProps = Infer<typeof userState>;        // State properties
type ActiveProps = Infer<typeof userState.active>; // Variant properties  
type SignalArgs = Infer<typeof updateSignal>;    // Signal arguments

Common utility types:

  • ExtractVariants<T> - Get variant names
  • ExtractSignals<T> - Get signal types
  • ExtractName<T> - Get state name
  • ArrayToRecord<T> - Convert state array to record

Error Types

StateFlow uses custom errors for framework issues:

class StateFlowError extends Error {
  name: "StateFlowError";
}

Common errors:

  • Redefining flows for same state variant
  • Dispatching signals during active transitions
  • Invalid state configuration (missing name/variants)

The StateFlow API provides type-safe, comprehensive feedback for predictable state management.

Previous
State visibility